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
// US-015: Asset and sub-account endpoints
use crate::futures::models::account::{WalletTransferParams, WalletTransferResponse};
use crate::rest::client::RestClient;
use crate::rest::error::AsterDexError;
use crate::rest::response::ApiResponse;
impl RestClient {
/// Transfer assets between wallets.
///
/// The `transfer_type` field identifies the wallet pair (e.g. spot→futures, futures→spot).
/// Consult the API docs for valid values.
///
/// # Errors
/// - `ConfigError` — credentials not set
/// - `ApiError` — invalid asset, amount, or transfer type
/// - `RateLimited` — HTTP 429
/// - `IpBanned` — HTTP 418
pub async fn wallet_transfer(
&self,
params: WalletTransferParams,
) -> Result<ApiResponse<WalletTransferResponse>, AsterDexError> {
let transfer_type_str = params.transfer_type.to_string();
self.signed_post(
"/fapi/v3/asset/wallet/transfer",
&[
("asset", params.asset.as_str()),
("amount", params.amount.as_str()),
("type", &transfer_type_str),
],
)
.await
}
/// Bind a sub-account to the current account.
///
/// The response structure is not yet documented — returned as raw JSON until API docs
/// are clarified. Refactor to a typed struct in a follow-up story.
///
/// # Errors
/// - `ConfigError` — credentials not set
/// - `ApiError` — invalid sub-account UID or binding already exists
/// - `RateLimited` — HTTP 429
/// - `IpBanned` — HTTP 418
pub async fn sub_account_bind(
&self,
sub_uid: &str,
) -> Result<ApiResponse<serde_json::Value>, AsterDexError> {
self.signed_post("/fapi/v3/apiKeySubAccountBindings", &[("subUid", sub_uid)])
.await
}
}
#[cfg(test)]
mod tests {
use super::*;
// US-015: wallet_transfer without credentials returns ConfigError
#[tokio::test]
async fn wallet_transfer_without_credentials_returns_config_error() {
let server = mockito::Server::new_async().await;
// No mock needed — error is returned before any HTTP call
let client = RestClient::new_public(&server.url()).unwrap();
let params = WalletTransferParams {
asset: "USDT".to_string(),
amount: "100.0".to_string(),
transfer_type: 1,
};
let result = client.wallet_transfer(params).await;
assert!(
matches!(result, Err(AsterDexError::ConfigError { .. })),
"expected ConfigError, got: {:?}",
result
);
drop(server);
}
}