use chrono::serde as chrono_serde;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use crate::common::*;
use crate::v2::rest::api_impl::*;
#[derive(Serialize, Debug)]
pub struct GetWithdrawal {
pub uuid: String,
}
impl_api!(GetWithdrawal => RespWithdrawalDetail : auth GET, "/api/v2/withdrawal");
#[derive(Serialize, Debug)]
pub struct GetWithdrawals {
#[serde(skip_serializing_if = "Option::is_none")]
pub currency: Option<String>,
#[serde(rename = "from", skip_serializing_if = "Option::is_none")]
pub from_timestamp: Option<DateTime>,
#[serde(rename = "to", skip_serializing_if = "Option::is_none")]
pub to_timestamp: Option<DateTime>,
#[serde(skip_serializing_if = "Option::is_none")]
pub state: Option<WithdrawalState>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pagination: Option<bool>,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub page_params: Option<PageParams>,
#[serde(skip_serializing_if = "Option::is_none")]
pub offset: Option<u64>,
}
impl_api!(GetWithdrawals => Vec<RespWithdrawalDetail> : auth GET, "/api/v2/withdrawals");
#[derive(Serialize, Debug)]
pub struct CreateWithdrawal {
pub currency: String,
pub withdraw_address_uuid: String,
pub amount: Decimal,
}
impl_api!(CreateWithdrawal => RespCreatedWithdraw : auth POST, "/api/v2/withdrawal");
#[derive(Serialize, Debug)]
pub struct GetWithdrawAddresses {
pub currency: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub pagination: Option<bool>,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub page_params: Option<PageParams>,
#[serde(skip_serializing_if = "Option::is_none")]
pub offset: Option<u64>,
}
impl_api!(GetWithdrawAddresses => Vec<WithdrawAddress> : auth GET, "/api/v2/withdraw_addresses");
#[derive(Deserialize, Eq, PartialEq, Default, Debug)]
#[serde(default)]
pub struct RespWithdrawalDetail {
pub uuid: String,
pub currency: String,
pub currency_version: String,
pub amount: Decimal,
pub fee: Decimal,
pub fee_currency: String,
pub txid: Option<String>,
#[serde(with = "chrono_serde::ts_seconds_option")]
pub created_at: Option<DateTime>,
#[serde(with = "chrono_serde::ts_seconds_option")]
pub updated_at: Option<DateTime>,
pub state: WithdrawalState,
}
#[derive(Deserialize, Eq, PartialEq, Debug)]
pub struct RespCreatedWithdraw {
#[serde(flatten)]
pub detail: RespWithdrawalDetail,
#[serde(default, rename = "type")]
pub transaction_direction: TransactionDirection,
pub transaction_type: String,
pub notes: Option<String>,
pub sender: Option<String>,
pub recipient: Option<String>,
}
#[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Debug)]
#[serde(rename_all = "snake_case")]
pub enum WithdrawalState {
Submitting,
Submitted,
Rejected,
Accepted,
Suspect,
Approved,
DelistedProcessing,
Reviewing,
Processing,
Retryable,
Sent,
Canceled,
Failed,
Pending,
Confirmed,
Overdue,
KgiManuallyProcessing,
KgiManuallyConfirmed,
KgiPossibleFailed,
SygnaVerifying,
Unknown,
}
impl WithdrawalState {
pub fn is_unknown(&self) -> bool {
self == &Self::Unknown
}
}
impl Default for WithdrawalState {
fn default() -> Self {
Self::Unknown
}
}
#[derive(Deserialize, Eq, PartialEq, Debug)]
#[serde(rename_all = "lowercase")]
pub enum TransactionDirection {
Internal,
External,
Unknown,
}
impl TransactionDirection {
pub fn is_unknown(&self) -> bool {
self == &Self::Unknown
}
}
impl Default for TransactionDirection {
fn default() -> Self {
Self::Unknown
}
}
#[derive(Deserialize, Eq, PartialEq, Debug)]
#[serde(rename_all = "lowercase")]
pub enum WithdrawAddressState {
Unverified,
Verified,
Disabled,
Unknown,
}
impl WithdrawAddressState {
pub fn is_unknown(&self) -> bool {
self == &Self::Unknown
}
}
impl Default for WithdrawAddressState {
fn default() -> Self {
Self::Unknown
}
}
#[derive(Deserialize, Eq, PartialEq, Default, Debug)]
pub struct WithdrawAddress {
pub uuid: String,
pub currency: String,
pub currency_version: String,
pub currency_protocol_name: Option<String>,
pub address: String,
pub extra_label: String,
#[serde(with = "chrono_serde::ts_seconds_option")]
pub created_at: Option<DateTime>,
#[serde(with = "chrono_serde::ts_seconds_option")]
pub deleted_at: Option<DateTime>,
pub state: Option<WithdrawAddressState>,
pub sygna_vasp_code: Option<String>,
pub sygna_user_type: Option<String>,
pub sygna_user_code: Option<String>,
pub is_internal: Option<bool>,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::util::test_util::*;
use chrono::{TimeZone, Utc};
use rust_decimal_macros::dec;
use surf::Client as HTTPClient;
use surf_vcr::VcrMode;
async fn create_client(cassette: &'static str) -> HTTPClient {
let mut path_builder = test_resource_path();
path_builder.push("rest");
path_builder.push("private");
path_builder.push("withdrawal");
path_builder.push(cassette);
create_test_recording_client(VcrMode::Replay, path_builder.as_path().to_str().unwrap())
.await
}
#[async_std::test]
async fn get_single_withdrawal() {
let params = GetWithdrawal {
uuid: "211120074215374658171".into(),
};
let resp = create_client("get_single_withdrawal.yaml")
.await
.send(params.to_request(&TEST_CREDENTIALS))
.await
.expect("Error while sending request");
let result: RespWithdrawalDetail = GetWithdrawal::read_response(resp.into()).await.unwrap();
assert_eq!(
result,
RespWithdrawalDetail {
uuid: "(test erased uuid)".into(),
currency: "sol".into(),
currency_version: "sol".into(),
amount: dec!(1.0),
fee: dec!(4.21265078),
fee_currency: "max".into(),
txid: Some("(test erased txid)".into()),
created_at: Some(Utc.timestamp(1637394145, 0)),
updated_at: Some(Utc.timestamp(1637394215, 0)),
state: WithdrawalState::Confirmed,
}
);
}
#[async_std::test]
async fn get_all_withdrawal() {
let params = GetWithdrawals {
currency: Some("sol".into()),
from_timestamp: None,
to_timestamp: None,
state: None,
pagination: None,
page_params: None,
offset: None,
};
let resp = create_client("get_all_withdrawal.yaml")
.await
.send(params.to_request(&TEST_CREDENTIALS))
.await
.expect("Error while sending request");
let result: Vec<RespWithdrawalDetail> =
GetWithdrawals::read_response(resp.into()).await.unwrap();
assert_eq!(
result,
vec![
RespWithdrawalDetail {
uuid: "(test erased uuid)".into(),
currency: "sol".into(),
currency_version: "sol".into(),
amount: dec!(1.0),
fee: dec!(4.21265078),
fee_currency: "max".into(),
txid: Some("(test erased txid)".into()),
created_at: Some(Utc.timestamp(1637394145, 0)),
updated_at: Some(Utc.timestamp(1637394215, 0)),
state: WithdrawalState::Confirmed,
},
RespWithdrawalDetail {
uuid: "(test erased uuid)".into(),
currency: "sol".into(),
currency_version: "sol".into(),
amount: dec!(4.32),
fee: dec!(4.60232158),
fee_currency: "max".into(),
txid: Some("(test erased txid)".into()),
created_at: Some(Utc.timestamp(1635983513, 0)),
updated_at: Some(Utc.timestamp(1635983641, 0)),
state: WithdrawalState::Confirmed,
}
]
);
}
#[async_std::test]
async fn create_withdrawal() {
let params = CreateWithdrawal {
currency: "sol".into(),
withdraw_address_uuid: "f79ad0c7-c321-4234-b0b3-4b3f8445dee9".into(),
amount: dec!(1),
};
let resp = create_client("create_withdrawal.yaml")
.await
.send(params.to_request(&TEST_CREDENTIALS))
.await
.expect("Error while sending request");
let result: RespCreatedWithdraw =
CreateWithdrawal::read_response(resp.into()).await.unwrap();
assert_eq!(
result,
RespCreatedWithdraw {
detail: RespWithdrawalDetail {
uuid: "(test erased uuid)".into(),
currency: "sol".into(),
currency_version: "sol".into(),
amount: dec!(1.0),
fee: dec!(4.21265078),
fee_currency: "max".into(),
txid: None,
created_at: Some(Utc.timestamp(1637394145, 0)),
updated_at: Some(Utc.timestamp(1637394145, 0)),
state: WithdrawalState::Submitted,
},
transaction_direction: TransactionDirection::External,
transaction_type: "external_send".into(),
notes: None,
sender: Some("(test erased sender)".into()),
recipient: Some("(test erased recipient)".into()),
}
);
}
#[async_std::test]
async fn get_withdraw_addresses() {
let params = GetWithdrawAddresses {
currency: "sol".into(),
pagination: None,
page_params: None,
offset: None,
};
let resp = create_client("get_withdraw_addresses.yaml")
.await
.send(params.to_request(&TEST_CREDENTIALS))
.await
.expect("Error while sending request");
let result: Vec<WithdrawAddress> = GetWithdrawAddresses::read_response(resp.into())
.await
.unwrap();
assert_eq!(
result,
vec![WithdrawAddress {
uuid: "(test erased uuid)".into(),
currency: "sol".into(),
currency_version: "sol".into(),
currency_protocol_name: None,
address: "(test erased address)".to_string(),
extra_label: "(test erased extra_label)".to_string(),
created_at: Some(Utc.timestamp(1635983472, 0)),
deleted_at: None,
state: None,
sygna_vasp_code: None,
sygna_user_type: None,
sygna_user_code: None,
is_internal: Some(false),
}]
);
}
}