1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
use crate::{
client::rest::KuCoinClient,
types::{
transfer::{AccountType, TransferData, TransferRequest, TransferType},
KuCoinResponse,
},
utils::errors::{KucoinErrors, KucoinResults},
};
use uuid::Uuid;
pub struct TransferHandler<'a> {
pub client: &'a KuCoinClient,
}
impl TransferRequest {
/// Creates a new transfer request with an auto-generated unique ID (`client_oid`).
///
/// # Arguments
/// * `currency` - The asset to transfer (e.g., "USDT").
/// * `amount` - The amount to transfer.
/// * `src_type` - The source account type (e.g., `Main`, `Trade`).
/// * `dest_type` - The destination account type.
/// * `transfer_type` - The nature of the transfer (e.g., `Internal`).
pub fn new(
currency: &str,
amount: f64,
src_type: AccountType,
dest_type: AccountType,
transfer_type: TransferType,
) -> Self {
TransferRequest {
amount: amount.to_string(),
client_oid: Uuid::new_v4().to_string(),
currency: currency.to_string(),
from_account_tag: None,
from_account_type: src_type,
from_user_id: None,
to_account_tag: None,
to_account_type: dest_type,
to_user_id: None,
transfer_request_type: transfer_type,
}
}
/// Sets the source trading pair symbol (e.g., "BTC-USDT").
/// Required only when the source is an `ISOLATED` margin account.
pub fn set_from_account_tag(mut self, symbol: &str) -> Self {
self.from_account_tag = Some(symbol.to_string());
self
}
/// Sets the destination trading pair symbol (e.g., "BTC-USDT").
/// Required only when the destination is an `ISOLATED` margin account.
pub fn set_to_account_tag(mut self, symbol: &str) -> Self {
self.to_account_tag = Some(symbol.to_string());
self
}
/// Sets the source User ID.
/// Required when transferring **from** a sub-account to a master account.
pub fn set_from_user_id(mut self, id: &str) -> Self {
self.from_user_id = Some(id.to_string());
self
}
/// Sets the destination User ID.
/// Required when transferring **to** a sub-account from a master account.
pub fn set_to_user_id(mut self, id: &str) -> Self {
self.to_user_id = Some(id.to_string());
self
}
/// This method **mutates** the provided `client` instance by overwriting its:
/// - `base_link` to `https://api.kucoin.com`
/// - `endpoint` to `/api/v3/accounts/universal-transfer`
///
/// # Argurments
/// - 'client' - Mutable instance of 'KuCoinClient'
///
/// # Returns
/// - Request Body in json-string.
fn build_body(&self) -> KucoinResults<String> {
// Validate payload.
let check_tag = |tag: &Option<String>, acc_type: &AccountType, name: &str| {
if tag.is_none() && matches!(&acc_type, AccountType::Isolated | AccountType::IsolatedV2)
{
return Err(KucoinErrors::MissingIsolatedTag(name.to_string()));
}
Ok(())
};
check_tag(&self.from_account_tag, &self.from_account_type, "Sender")?;
check_tag(&self.to_account_tag, &self.to_account_type, "Receiver")?;
let json = serde_json::to_string(&self)?;
Ok(json)
}
}
impl<'a> TransferHandler<'a> {
/// Executes a universal transfer between accounts.
///
/// # Panics
/// Panics immediately if request validation fails (e.g., missing tags for Isolated Margin).
///
/// # Returns
/// The transaction receipt on success, or a `reqwest::Error` if the network request fails.
pub async fn execute(
&self,
request: TransferRequest,
) -> KucoinResults<KuCoinResponse<TransferData>> {
let body = request.build_body()?; // Get JSON body
let endpoint = "/api/v3/accounts/universal-transfer";
let res = self
.client
.send::<KuCoinResponse<TransferData>>("POST", &body, endpoint)
.await?;
Ok(res)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::client::rest::Credentials;
use std::env;
#[tokio::test]
async fn test_transfer_internal() {
// 1. Setup Credentials
let credentials = Credentials::new(
&env::var("api_key").unwrap(),
&env::var("api_secret").unwrap(),
&env::var("api_passphrase").unwrap(),
);
// 2. Initialize Client
let client = KuCoinClient::new(credentials);
// 3. Generate request.
let request = TransferRequest::new(
"BTC",
1.0,
AccountType::Main,
AccountType::Trade,
TransferType::Internal,
);
// 4. Execute tranaction.
match client.transfer().execute(request).await {
Ok(result) => println!("Transfer: {:#?}", result),
Err(e) => panic!("Transfer failed: {}", e),
}
}
}