1use serde::{Deserialize, Serialize};
4
5use crate::client::OkxClient;
6use crate::error::Error;
7use crate::model::NumberString;
8use crate::transport::Transport;
9
10const NON_TRADABLE_ASSETS: &str = "/api/v5/asset/non-tradable-assets";
11const DEPOSIT_ADDRESS: &str = "/api/v5/asset/deposit-address";
12const BALANCES: &str = "/api/v5/asset/balances";
13const TRANSFER: &str = "/api/v5/asset/transfer";
14const TRANSFER_STATE: &str = "/api/v5/asset/transfer-state";
15const WITHDRAWAL: &str = "/api/v5/asset/withdrawal";
16const DEPOSIT_HISTORY: &str = "/api/v5/asset/deposit-history";
17const CURRENCIES: &str = "/api/v5/asset/currencies";
18const PURCHASE_REDEMPT: &str = "/api/v5/asset/purchase_redempt";
19const BILLS: &str = "/api/v5/asset/bills";
20const DEPOSIT_LIGHTNING: &str = "/api/v5/asset/deposit-lightning";
21const WITHDRAWAL_LIGHTNING: &str = "/api/v5/asset/withdrawal-lightning";
22const CANCEL_WITHDRAWAL: &str = "/api/v5/asset/cancel-withdrawal";
23const CONVERT_DUST_ASSETS: &str = "/api/v5/asset/convert-dust-assets";
24const ASSET_VALUATION: &str = "/api/v5/asset/asset-valuation";
25const WITHDRAWAL_HISTORY: &str = "/api/v5/asset/withdrawal-history";
26const DEPOSIT_WITHDRAW_STATUS: &str = "/api/v5/asset/deposit-withdraw-status";
27
28pub struct Funding<'a, T> {
34 client: &'a OkxClient<T>,
35}
36
37impl<'a, T: Transport> Funding<'a, T> {
38 pub(crate) fn new(client: &'a OkxClient<T>) -> Self {
39 Self { client }
40 }
41
42 pub async fn get_currencies(&self, ccy: Option<&str>) -> Result<Vec<Currency>, Error> {
51 let query = CcyQuery { ccy };
52 self.client.get(CURRENCIES, &query, true).await
53 }
54
55 pub async fn get_balances(&self, ccy: Option<&str>) -> Result<Vec<FundingBalance>, Error> {
63 let query = CcyQuery { ccy };
64 self.client.get(BALANCES, &query, true).await
65 }
66
67 pub async fn get_non_tradable_assets(
75 &self,
76 ccy: Option<&str>,
77 ) -> Result<Vec<NonTradableAsset>, Error> {
78 let query = CcyQuery { ccy };
79 self.client.get(NON_TRADABLE_ASSETS, &query, true).await
80 }
81
82 pub async fn get_deposit_address(&self, ccy: &str) -> Result<Vec<DepositAddress>, Error> {
90 let query = RequiredCcyQuery { ccy };
91 self.client.get(DEPOSIT_ADDRESS, &query, true).await
92 }
93
94 pub async fn funds_transfer(
102 &self,
103 request: &FundsTransferRequest,
104 ) -> Result<Vec<TransferResult>, Error> {
105 self.client.post(TRANSFER, request, true).await
106 }
107
108 pub async fn transfer_state(
116 &self,
117 trans_id: &str,
118 transfer_type: Option<&str>,
119 ) -> Result<Vec<TransferState>, Error> {
120 let query = TransferStateQuery {
121 trans_id,
122 transfer_type,
123 };
124 self.client.get(TRANSFER_STATE, &query, true).await
125 }
126
127 pub async fn withdrawal(
137 &self,
138 request: &WithdrawalRequest,
139 ) -> Result<Vec<WithdrawalResult>, Error> {
140 self.client.post(WITHDRAWAL, request, true).await
141 }
142
143 pub async fn get_deposit_history(
151 &self,
152 request: &DepositHistoryRequest,
153 ) -> Result<Vec<DepositRecord>, Error> {
154 self.client.get(DEPOSIT_HISTORY, request, true).await
155 }
156
157 pub async fn purchase_redempt(
166 &self,
167 ccy: &str,
168 amt: &str,
169 side: &str,
170 rate: &str,
171 ) -> Result<Vec<PurchaseRedemptResult>, Error> {
172 let body = PurchaseRedemptBody {
173 ccy,
174 amt,
175 side,
176 rate,
177 };
178 self.client.post(PURCHASE_REDEMPT, &body, true).await
179 }
180
181 pub async fn get_bills(
189 &self,
190 request: &FundingBillsRequest,
191 ) -> Result<Vec<FundingBill>, Error> {
192 self.client.get(BILLS, request, true).await
193 }
194
195 pub async fn get_deposit_lightning(
203 &self,
204 request: &DepositLightningRequest,
205 ) -> Result<Vec<DepositLightning>, Error> {
206 self.client.get(DEPOSIT_LIGHTNING, request, true).await
207 }
208
209 pub async fn withdrawal_lightning(
217 &self,
218 request: &WithdrawalLightningRequest,
219 ) -> Result<Vec<WithdrawalLightning>, Error> {
220 self.client.post(WITHDRAWAL_LIGHTNING, request, true).await
221 }
222
223 pub async fn cancel_withdrawal(&self, wd_id: &str) -> Result<Vec<WithdrawalResult>, Error> {
231 let body = WithdrawalIdBody { wd_id };
232 self.client.post(CANCEL_WITHDRAWAL, &body, true).await
233 }
234
235 pub async fn convert_dust_assets(
243 &self,
244 ccy: &[&str],
245 ) -> Result<Vec<ConvertDustAssetsResult>, Error> {
246 let body = ConvertDustAssetsBody { ccy };
247 self.client.post(CONVERT_DUST_ASSETS, &body, true).await
248 }
249
250 pub async fn get_asset_valuation(
258 &self,
259 ccy: Option<&str>,
260 ) -> Result<Vec<AssetValuation>, Error> {
261 let query = CcyQuery { ccy };
262 self.client.get(ASSET_VALUATION, &query, true).await
263 }
264
265 pub async fn get_deposit_withdraw_status(
273 &self,
274 request: &DepositWithdrawStatusRequest,
275 ) -> Result<Vec<DepositWithdrawStatus>, Error> {
276 self.client
277 .get(DEPOSIT_WITHDRAW_STATUS, request, true)
278 .await
279 }
280
281 pub async fn get_withdrawal_history(
289 &self,
290 request: &WithdrawalHistoryRequest,
291 ) -> Result<Vec<WithdrawalRecord>, Error> {
292 self.client.get(WITHDRAWAL_HISTORY, request, true).await
293 }
294}
295
296#[derive(Debug, Serialize)]
297struct CcyQuery<'a> {
298 #[serde(skip_serializing_if = "Option::is_none")]
299 ccy: Option<&'a str>,
300}
301
302#[derive(Debug, Serialize)]
303struct RequiredCcyQuery<'a> {
304 ccy: &'a str,
305}
306
307#[derive(Debug, Serialize)]
308#[serde(rename_all = "camelCase")]
309struct TransferStateQuery<'a> {
310 #[serde(rename = "transId")]
311 trans_id: &'a str,
312 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
313 transfer_type: Option<&'a str>,
314}
315
316#[derive(Debug, Serialize)]
317struct PurchaseRedemptBody<'a> {
318 ccy: &'a str,
319 amt: &'a str,
320 side: &'a str,
321 rate: &'a str,
322}
323
324#[derive(Debug, Serialize)]
325#[serde(rename_all = "camelCase")]
326struct WithdrawalIdBody<'a> {
327 #[serde(rename = "wdId")]
328 wd_id: &'a str,
329}
330
331#[derive(Debug, Serialize)]
332struct ConvertDustAssetsBody<'a> {
333 ccy: &'a [&'a str],
334}
335
336#[derive(Debug, Clone, Serialize)]
338#[serde(rename_all = "camelCase")]
339pub struct FundsTransferRequest {
340 ccy: String,
341 amt: String,
342 #[serde(rename = "from")]
343 from_account: String,
344 to: String,
345 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
346 transfer_type: Option<String>,
347 #[serde(rename = "subAcct", skip_serializing_if = "Option::is_none")]
348 sub_account: Option<String>,
349 #[serde(rename = "instId", skip_serializing_if = "Option::is_none")]
350 inst_id: Option<String>,
351 #[serde(rename = "toInstId", skip_serializing_if = "Option::is_none")]
352 to_inst_id: Option<String>,
353 #[serde(rename = "loanTrans", skip_serializing_if = "Option::is_none")]
354 loan_transfer: Option<String>,
355}
356
357impl FundsTransferRequest {
358 pub fn new(
360 ccy: impl Into<String>,
361 amt: impl Into<String>,
362 from_account: impl Into<String>,
363 to: impl Into<String>,
364 ) -> Self {
365 Self {
366 ccy: ccy.into(),
367 amt: amt.into(),
368 from_account: from_account.into(),
369 to: to.into(),
370 transfer_type: None,
371 sub_account: None,
372 inst_id: None,
373 to_inst_id: None,
374 loan_transfer: None,
375 }
376 }
377
378 pub fn transfer_type(mut self, transfer_type: impl Into<String>) -> Self {
380 self.transfer_type = Some(transfer_type.into());
381 self
382 }
383
384 pub fn sub_account(mut self, sub_account: impl Into<String>) -> Self {
386 self.sub_account = Some(sub_account.into());
387 self
388 }
389
390 pub fn inst_id(mut self, inst_id: impl Into<String>) -> Self {
392 self.inst_id = Some(inst_id.into());
393 self
394 }
395
396 pub fn to_inst_id(mut self, to_inst_id: impl Into<String>) -> Self {
398 self.to_inst_id = Some(to_inst_id.into());
399 self
400 }
401
402 pub fn loan_transfer(mut self, loan_transfer: impl Into<String>) -> Self {
404 self.loan_transfer = Some(loan_transfer.into());
405 self
406 }
407}
408
409#[derive(Debug, Clone, Serialize)]
411#[serde(rename_all = "camelCase")]
412pub struct WithdrawalRequest {
413 ccy: String,
414 amt: String,
415 dest: String,
416 #[serde(rename = "toAddr")]
417 to_addr: String,
418 #[serde(skip_serializing_if = "Option::is_none")]
419 chain: Option<String>,
420 #[serde(rename = "areaCode", skip_serializing_if = "Option::is_none")]
421 area_code: Option<String>,
422 #[serde(rename = "clientId", skip_serializing_if = "Option::is_none")]
423 client_id: Option<String>,
424 #[serde(rename = "toAddrType", skip_serializing_if = "Option::is_none")]
425 to_addr_type: Option<String>,
426}
427
428impl WithdrawalRequest {
429 pub fn new(
431 ccy: impl Into<String>,
432 amt: impl Into<String>,
433 dest: impl Into<String>,
434 to_addr: impl Into<String>,
435 ) -> Self {
436 Self {
437 ccy: ccy.into(),
438 amt: amt.into(),
439 dest: dest.into(),
440 to_addr: to_addr.into(),
441 chain: None,
442 area_code: None,
443 client_id: None,
444 to_addr_type: None,
445 }
446 }
447
448 pub fn chain(mut self, chain: impl Into<String>) -> Self {
450 self.chain = Some(chain.into());
451 self
452 }
453
454 pub fn area_code(mut self, area_code: impl Into<String>) -> Self {
456 self.area_code = Some(area_code.into());
457 self
458 }
459
460 pub fn client_id(mut self, client_id: impl Into<String>) -> Self {
462 self.client_id = Some(client_id.into());
463 self
464 }
465
466 pub fn to_addr_type(mut self, to_addr_type: impl Into<String>) -> Self {
468 self.to_addr_type = Some(to_addr_type.into());
469 self
470 }
471}
472
473#[derive(Debug, Clone, Default, Serialize)]
475#[serde(rename_all = "camelCase")]
476pub struct DepositHistoryRequest {
477 #[serde(skip_serializing_if = "Option::is_none")]
478 ccy: Option<String>,
479 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
480 deposit_type: Option<String>,
481 #[serde(skip_serializing_if = "Option::is_none")]
482 state: Option<String>,
483 #[serde(skip_serializing_if = "Option::is_none")]
484 after: Option<String>,
485 #[serde(skip_serializing_if = "Option::is_none")]
486 before: Option<String>,
487 #[serde(skip_serializing_if = "Option::is_none")]
488 limit: Option<u32>,
489 #[serde(rename = "txId", skip_serializing_if = "Option::is_none")]
490 tx_id: Option<String>,
491 #[serde(rename = "depId", skip_serializing_if = "Option::is_none")]
492 dep_id: Option<String>,
493 #[serde(rename = "fromWdId", skip_serializing_if = "Option::is_none")]
494 from_wd_id: Option<String>,
495}
496
497impl DepositHistoryRequest {
498 pub fn new() -> Self {
500 Self::default()
501 }
502
503 pub fn currency(mut self, ccy: impl Into<String>) -> Self {
505 self.ccy = Some(ccy.into());
506 self
507 }
508
509 pub fn deposit_type(mut self, deposit_type: impl Into<String>) -> Self {
511 self.deposit_type = Some(deposit_type.into());
512 self
513 }
514
515 pub fn state(mut self, state: impl Into<String>) -> Self {
517 self.state = Some(state.into());
518 self
519 }
520
521 pub fn after(mut self, after: impl Into<String>) -> Self {
523 self.after = Some(after.into());
524 self
525 }
526
527 pub fn before(mut self, before: impl Into<String>) -> Self {
529 self.before = Some(before.into());
530 self
531 }
532
533 pub fn limit(mut self, limit: u32) -> Self {
535 self.limit = Some(limit);
536 self
537 }
538
539 pub fn tx_id(mut self, tx_id: impl Into<String>) -> Self {
541 self.tx_id = Some(tx_id.into());
542 self
543 }
544
545 pub fn dep_id(mut self, dep_id: impl Into<String>) -> Self {
547 self.dep_id = Some(dep_id.into());
548 self
549 }
550
551 pub fn from_wd_id(mut self, from_wd_id: impl Into<String>) -> Self {
553 self.from_wd_id = Some(from_wd_id.into());
554 self
555 }
556}
557
558#[derive(Debug, Clone, Default, Serialize)]
560#[serde(rename_all = "camelCase")]
561pub struct WithdrawalHistoryRequest {
562 #[serde(skip_serializing_if = "Option::is_none")]
563 ccy: Option<String>,
564 #[serde(rename = "wdId", skip_serializing_if = "Option::is_none")]
565 wd_id: Option<String>,
566 #[serde(rename = "clientId", skip_serializing_if = "Option::is_none")]
567 client_id: Option<String>,
568 #[serde(rename = "txId", skip_serializing_if = "Option::is_none")]
569 tx_id: Option<String>,
570 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
571 withdrawal_type: Option<String>,
572 #[serde(skip_serializing_if = "Option::is_none")]
573 state: Option<String>,
574 #[serde(skip_serializing_if = "Option::is_none")]
575 after: Option<String>,
576 #[serde(skip_serializing_if = "Option::is_none")]
577 before: Option<String>,
578 #[serde(skip_serializing_if = "Option::is_none")]
579 limit: Option<u32>,
580 #[serde(rename = "toAddrType", skip_serializing_if = "Option::is_none")]
581 to_addr_type: Option<String>,
582}
583
584impl WithdrawalHistoryRequest {
585 pub fn new() -> Self {
587 Self::default()
588 }
589
590 pub fn currency(mut self, ccy: impl Into<String>) -> Self {
592 self.ccy = Some(ccy.into());
593 self
594 }
595
596 pub fn withdrawal_id(mut self, wd_id: impl Into<String>) -> Self {
598 self.wd_id = Some(wd_id.into());
599 self
600 }
601
602 pub fn client_id(mut self, client_id: impl Into<String>) -> Self {
604 self.client_id = Some(client_id.into());
605 self
606 }
607
608 pub fn tx_id(mut self, tx_id: impl Into<String>) -> Self {
610 self.tx_id = Some(tx_id.into());
611 self
612 }
613
614 pub fn withdrawal_type(mut self, withdrawal_type: impl Into<String>) -> Self {
616 self.withdrawal_type = Some(withdrawal_type.into());
617 self
618 }
619
620 pub fn state(mut self, state: impl Into<String>) -> Self {
622 self.state = Some(state.into());
623 self
624 }
625
626 pub fn after(mut self, after: impl Into<String>) -> Self {
628 self.after = Some(after.into());
629 self
630 }
631
632 pub fn before(mut self, before: impl Into<String>) -> Self {
634 self.before = Some(before.into());
635 self
636 }
637
638 pub fn limit(mut self, limit: u32) -> Self {
640 self.limit = Some(limit);
641 self
642 }
643
644 pub fn to_addr_type(mut self, to_addr_type: impl Into<String>) -> Self {
646 self.to_addr_type = Some(to_addr_type.into());
647 self
648 }
649}
650
651#[derive(Debug, Clone, Default, Serialize)]
653pub struct FundingBillsRequest {
654 #[serde(skip_serializing_if = "Option::is_none")]
655 ccy: Option<String>,
656 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
657 bill_type: Option<String>,
658 #[serde(skip_serializing_if = "Option::is_none")]
659 after: Option<String>,
660 #[serde(skip_serializing_if = "Option::is_none")]
661 before: Option<String>,
662 #[serde(skip_serializing_if = "Option::is_none")]
663 limit: Option<u32>,
664}
665
666impl FundingBillsRequest {
667 pub fn new() -> Self {
669 Self::default()
670 }
671
672 pub fn currency(mut self, ccy: impl Into<String>) -> Self {
674 self.ccy = Some(ccy.into());
675 self
676 }
677
678 pub fn bill_type(mut self, bill_type: impl Into<String>) -> Self {
680 self.bill_type = Some(bill_type.into());
681 self
682 }
683
684 pub fn after(mut self, after: impl Into<String>) -> Self {
686 self.after = Some(after.into());
687 self
688 }
689
690 pub fn before(mut self, before: impl Into<String>) -> Self {
692 self.before = Some(before.into());
693 self
694 }
695
696 pub fn limit(mut self, limit: u32) -> Self {
698 self.limit = Some(limit);
699 self
700 }
701}
702
703#[derive(Debug, Clone, Serialize)]
705pub struct DepositLightningRequest {
706 ccy: String,
707 amt: String,
708 #[serde(skip_serializing_if = "Option::is_none")]
709 to: Option<String>,
710}
711
712impl DepositLightningRequest {
713 pub fn new(ccy: impl Into<String>, amt: impl Into<String>) -> Self {
715 Self {
716 ccy: ccy.into(),
717 amt: amt.into(),
718 to: None,
719 }
720 }
721
722 pub fn to(mut self, to: impl Into<String>) -> Self {
724 self.to = Some(to.into());
725 self
726 }
727}
728
729#[derive(Debug, Clone, Serialize)]
731pub struct WithdrawalLightningRequest {
732 ccy: String,
733 invoice: String,
734 #[serde(skip_serializing_if = "Option::is_none")]
735 memo: Option<String>,
736}
737
738impl WithdrawalLightningRequest {
739 pub fn new(ccy: impl Into<String>, invoice: impl Into<String>) -> Self {
741 Self {
742 ccy: ccy.into(),
743 invoice: invoice.into(),
744 memo: None,
745 }
746 }
747
748 pub fn memo(mut self, memo: impl Into<String>) -> Self {
750 self.memo = Some(memo.into());
751 self
752 }
753}
754
755#[derive(Debug, Clone, Default, Serialize)]
757#[serde(rename_all = "camelCase")]
758pub struct DepositWithdrawStatusRequest {
759 #[serde(rename = "wdId", skip_serializing_if = "Option::is_none")]
760 wd_id: Option<String>,
761 #[serde(rename = "txId", skip_serializing_if = "Option::is_none")]
762 tx_id: Option<String>,
763 #[serde(skip_serializing_if = "Option::is_none")]
764 ccy: Option<String>,
765 #[serde(skip_serializing_if = "Option::is_none")]
766 to: Option<String>,
767 #[serde(skip_serializing_if = "Option::is_none")]
768 chain: Option<String>,
769}
770
771impl DepositWithdrawStatusRequest {
772 pub fn new() -> Self {
774 Self::default()
775 }
776
777 pub fn withdrawal_id(mut self, wd_id: impl Into<String>) -> Self {
779 self.wd_id = Some(wd_id.into());
780 self
781 }
782
783 pub fn tx_id(mut self, tx_id: impl Into<String>) -> Self {
785 self.tx_id = Some(tx_id.into());
786 self
787 }
788
789 pub fn currency(mut self, ccy: impl Into<String>) -> Self {
791 self.ccy = Some(ccy.into());
792 self
793 }
794
795 pub fn to(mut self, to: impl Into<String>) -> Self {
797 self.to = Some(to.into());
798 self
799 }
800
801 pub fn chain(mut self, chain: impl Into<String>) -> Self {
803 self.chain = Some(chain.into());
804 self
805 }
806}
807
808#[derive(Debug, Clone, Default, Deserialize)]
810#[serde(rename_all = "camelCase")]
811#[non_exhaustive]
812pub struct Currency {
813 #[serde(default)]
815 pub ccy: String,
816 #[serde(default)]
818 pub name: String,
819 #[serde(default)]
821 pub chain: String,
822 #[serde(default, rename = "minWd")]
824 pub min_wd: NumberString,
825 #[serde(default, rename = "minDep")]
827 pub min_dep: NumberString,
828 #[serde(default, rename = "minFee")]
830 pub min_fee: NumberString,
831 #[serde(default, rename = "canDep")]
833 pub can_dep: bool,
834 #[serde(default, rename = "canWd")]
836 pub can_wd: bool,
837 #[serde(default, rename = "canInternal")]
839 pub can_internal: bool,
840}
841
842#[derive(Debug, Clone, Default, Deserialize)]
844#[serde(rename_all = "camelCase")]
845#[non_exhaustive]
846pub struct FundingBalance {
847 #[serde(default)]
849 pub ccy: String,
850 #[serde(default)]
852 pub bal: NumberString,
853 #[serde(default)]
855 pub frozen_bal: NumberString,
856 #[serde(default)]
858 pub avail_bal: NumberString,
859}
860
861#[derive(Debug, Clone, Default, Deserialize)]
863#[serde(rename_all = "camelCase")]
864#[non_exhaustive]
865pub struct NonTradableAsset {
866 #[serde(default)]
868 pub ccy: String,
869 #[serde(default)]
871 pub amt: NumberString,
872 #[serde(default, rename = "type")]
874 pub asset_type: String,
875}
876
877#[derive(Debug, Clone, Default, Deserialize)]
879#[serde(rename_all = "camelCase")]
880#[non_exhaustive]
881pub struct DepositAddress {
882 #[serde(default)]
884 pub ccy: String,
885 #[serde(default)]
887 pub chain: String,
888 #[serde(default)]
890 pub addr: String,
891 #[serde(default)]
893 pub tag: String,
894 #[serde(default)]
896 pub selected: bool,
897}
898
899#[derive(Debug, Clone, Default, Deserialize)]
901#[serde(rename_all = "camelCase")]
902#[non_exhaustive]
903pub struct TransferResult {
904 #[serde(default, rename = "transId")]
906 pub trans_id: String,
907 #[serde(default)]
909 pub ccy: String,
910 #[serde(default)]
912 pub amt: NumberString,
913}
914
915#[derive(Debug, Clone, Default, Deserialize)]
917#[serde(rename_all = "camelCase")]
918#[non_exhaustive]
919pub struct TransferState {
920 #[serde(default, rename = "transId")]
922 pub trans_id: String,
923 #[serde(default)]
925 pub state: String,
926 #[serde(default)]
928 pub ccy: String,
929 #[serde(default)]
931 pub amt: NumberString,
932 #[serde(default, rename = "from")]
934 pub from_account: String,
935 #[serde(default)]
937 pub to: String,
938}
939
940#[derive(Debug, Clone, Default, Deserialize)]
942#[serde(rename_all = "camelCase")]
943#[non_exhaustive]
944pub struct WithdrawalResult {
945 #[serde(default, rename = "wdId")]
947 pub wd_id: String,
948 #[serde(default, rename = "clientId")]
950 pub client_id: String,
951 #[serde(default)]
953 pub ccy: String,
954 #[serde(default)]
956 pub amt: NumberString,
957}
958
959#[derive(Debug, Clone, Default, Deserialize)]
961#[serde(rename_all = "camelCase")]
962#[non_exhaustive]
963pub struct DepositRecord {
964 #[serde(default, rename = "depId")]
966 pub dep_id: String,
967 #[serde(default, rename = "txId")]
969 pub tx_id: String,
970 #[serde(default)]
972 pub ccy: String,
973 #[serde(default)]
975 pub chain: String,
976 #[serde(default)]
978 pub amt: NumberString,
979 #[serde(default)]
981 pub state: String,
982 #[serde(default)]
984 pub ts: NumberString,
985}
986
987#[derive(Debug, Clone, Default, Deserialize)]
989#[serde(rename_all = "camelCase")]
990#[non_exhaustive]
991pub struct WithdrawalRecord {
992 #[serde(default, rename = "wdId")]
994 pub wd_id: String,
995 #[serde(default, rename = "clientId")]
997 pub client_id: String,
998 #[serde(default, rename = "txId")]
1000 pub tx_id: String,
1001 #[serde(default)]
1003 pub ccy: String,
1004 #[serde(default)]
1006 pub chain: String,
1007 #[serde(default)]
1009 pub amt: NumberString,
1010 #[serde(default)]
1012 pub fee: NumberString,
1013 #[serde(default)]
1015 pub state: String,
1016 #[serde(default)]
1018 pub ts: NumberString,
1019}
1020
1021#[derive(Debug, Clone, Default, Deserialize)]
1023#[serde(rename_all = "camelCase")]
1024#[non_exhaustive]
1025pub struct FundingBill {
1026 #[serde(default, rename = "billId")]
1028 pub bill_id: String,
1029 #[serde(default)]
1031 pub ccy: String,
1032 #[serde(default)]
1034 pub bal_chg: NumberString,
1035 #[serde(default)]
1037 pub bal: NumberString,
1038 #[serde(default, rename = "type")]
1040 pub bill_type: String,
1041 #[serde(default)]
1043 pub ts: NumberString,
1044}
1045
1046#[derive(Debug, Clone, Default, Deserialize)]
1048#[serde(rename_all = "camelCase")]
1049#[non_exhaustive]
1050pub struct DepositLightning {
1051 #[serde(default)]
1053 pub ccy: String,
1054 #[serde(default)]
1056 pub amt: NumberString,
1057 #[serde(default)]
1059 pub invoice: String,
1060 #[serde(default)]
1062 pub to: String,
1063}
1064
1065#[derive(Debug, Clone, Default, Deserialize)]
1067#[serde(rename_all = "camelCase")]
1068#[non_exhaustive]
1069pub struct WithdrawalLightning {
1070 #[serde(default)]
1072 pub ccy: String,
1073 #[serde(default)]
1075 pub amt: NumberString,
1076 #[serde(default, rename = "wdId")]
1078 pub wd_id: String,
1079}
1080
1081#[derive(Debug, Clone, Default, Deserialize)]
1083#[serde(rename_all = "camelCase")]
1084#[non_exhaustive]
1085pub struct AssetValuation {
1086 #[serde(default)]
1088 pub details: AssetValuationDetails,
1089 #[serde(default, rename = "totalBal")]
1091 pub total_bal: NumberString,
1092}
1093
1094#[derive(Debug, Clone, Default, Deserialize)]
1096#[serde(rename_all = "camelCase")]
1097#[non_exhaustive]
1098pub struct AssetValuationDetails {
1099 #[serde(default)]
1101 pub funding: NumberString,
1102 #[serde(default)]
1104 pub trading: NumberString,
1105 #[serde(default)]
1107 pub earn: NumberString,
1108 #[serde(default)]
1110 pub classic: NumberString,
1111}
1112
1113#[derive(Debug, Clone, Default, Deserialize)]
1115#[serde(rename_all = "camelCase")]
1116#[non_exhaustive]
1117pub struct DepositWithdrawStatus {
1118 #[serde(default, rename = "wdId")]
1120 pub wd_id: String,
1121 #[serde(default, rename = "txId")]
1123 pub tx_id: String,
1124 #[serde(default)]
1126 pub ccy: String,
1127 #[serde(default)]
1129 pub chain: String,
1130 #[serde(default)]
1132 pub to: String,
1133 #[serde(default)]
1135 pub state: String,
1136}
1137
1138#[derive(Debug, Clone, Default, Deserialize)]
1140#[serde(rename_all = "camelCase")]
1141#[non_exhaustive]
1142pub struct ConvertDustAssetsResult {
1143 #[serde(default)]
1145 pub ccy: String,
1146 #[serde(default)]
1148 pub amt: NumberString,
1149}
1150
1151#[derive(Debug, Clone, Default, Deserialize)]
1153#[serde(rename_all = "camelCase")]
1154#[non_exhaustive]
1155pub struct PurchaseRedemptResult {
1156 #[serde(default)]
1158 pub ccy: String,
1159 #[serde(default)]
1161 pub amt: NumberString,
1162 #[serde(default)]
1164 pub side: String,
1165}
1166
1167#[cfg(test)]
1168mod tests {
1169 use super::*;
1170
1171 #[test]
1172 fn funds_transfer_request_omits_unset_optional_fields() {
1173 let value =
1174 serde_json::to_value(FundsTransferRequest::new("USDT", "1", "6", "18")).unwrap();
1175
1176 assert_eq!(value["ccy"], "USDT");
1177 assert_eq!(value["amt"], "1");
1178 assert_eq!(value["from"], "6");
1179 assert_eq!(value["to"], "18");
1180 assert!(value.get("subAcct").is_none());
1181 assert!(value.get("instId").is_none());
1182 assert!(value.get("loanTrans").is_none());
1183 }
1184
1185 #[test]
1186 fn withdrawal_request_omits_unset_optional_fields() {
1187 let value =
1188 serde_json::to_value(WithdrawalRequest::new("USDT", "1", "3", "example")).unwrap();
1189
1190 assert_eq!(value["ccy"], "USDT");
1191 assert_eq!(value["amt"], "1");
1192 assert_eq!(value["dest"], "3");
1193 assert_eq!(value["toAddr"], "example");
1194 assert!(value.get("chain").is_none());
1195 assert!(value.get("areaCode").is_none());
1196 assert!(value.get("toAddrType").is_none());
1197 }
1198
1199 #[test]
1200 fn history_requests_omit_unset_optional_fields() {
1201 let deposit = serde_urlencoded::to_string(DepositHistoryRequest::new().limit(5)).unwrap();
1202 assert_eq!(deposit, "limit=5");
1203
1204 let withdrawal =
1205 serde_urlencoded::to_string(WithdrawalHistoryRequest::new().to_addr_type("1")).unwrap();
1206 assert_eq!(withdrawal, "toAddrType=1");
1207 }
1208
1209 #[test]
1210 fn deposit_withdraw_status_request_omits_unset_optional_fields() {
1211 let query = serde_urlencoded::to_string(
1212 DepositWithdrawStatusRequest::new()
1213 .currency("USDT")
1214 .chain("USDT-TRC20"),
1215 )
1216 .unwrap();
1217
1218 assert_eq!(query, "ccy=USDT&chain=USDT-TRC20");
1219 }
1220}