use serde::Deserialize;
use crate::account::holdings::account::AccountKind;
use crate::account::settings::mode::{TradingMode, known_mode};
use crate::client::http::codes;
use crate::client::{ApiError, Call, Transport, read};
use crate::utils::lenient::{blank_dec, list, unsigned, zero_dec};
use crate::utils::{AccountIndex, Address, Dec};
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct OwnedAccount {
pub index: AccountIndex,
#[serde(rename = "account_type")]
pub kind: AccountKind,
pub l1_address: Address,
#[serde(rename = "account_trading_mode", default, deserialize_with = "known_mode")]
pub trading_mode: Option<TradingMode>,
#[serde(default)]
pub status: u8,
#[serde(default, deserialize_with = "zero_dec")]
pub collateral: Dec,
#[serde(default, deserialize_with = "blank_dec")]
pub available_balance: Option<Dec>,
#[serde(default, deserialize_with = "unsigned")]
pub cancel_all_time: u64,
#[serde(default, deserialize_with = "unsigned")]
pub total_order_count: u64,
#[serde(default, deserialize_with = "unsigned")]
pub total_isolated_order_count: u64,
#[serde(default, deserialize_with = "unsigned")]
pub pending_order_count: u64,
#[serde(default, deserialize_with = "unsigned")]
pub transaction_time: u64,
}
impl OwnedAccount {
#[must_use]
pub const fn is_master(&self) -> bool {
matches!(self.kind, AccountKind::Master)
}
#[must_use]
pub const fn is_funded(&self) -> bool {
self.collateral.is_positive()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Deserialize)]
pub struct OwnedAccounts {
#[serde(default)]
pub l1_address: Option<Address>,
#[serde(rename = "sub_accounts", default, deserialize_with = "list")]
pub accounts: Vec<OwnedAccount>,
#[serde(default)]
pub next_cursor: Option<String>,
}
impl OwnedAccounts {
#[must_use]
pub fn master(&self) -> Option<&OwnedAccount> {
let master = self.accounts.iter().filter(|account| account.is_master()).min_by_key(|account| account.index);
master.or_else(|| self.accounts.iter().min_by_key(|account| account.index))
}
pub fn sub_accounts(&self) -> impl Iterator<Item = &OwnedAccount> {
let master = self.master().map(|master| master.index);
self.accounts.iter().filter(move |account| Some(account.index) != master)
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.accounts.is_empty()
}
}
#[must_use]
pub fn accounts_by_l1_address_call(owner: Address, cursor: Option<&str>) -> Call {
Call::get("accountsByL1Address").field("l1_address", owner.to_hex()).maybe("cursor", cursor)
}
pub async fn accounts_by_l1_address(
transport: &impl Transport,
owner: Address,
cursor: Option<&str>,
) -> Result<OwnedAccounts, ApiError> {
match read::<OwnedAccounts>(transport, accounts_by_l1_address_call(owner, cursor)).await {
Err(error) if error.code() == Some(codes::ACCOUNT_NOT_FOUND) => {
Ok(OwnedAccounts { l1_address: Some(owner), ..OwnedAccounts::default() })
}
answer => answer,
}
}
pub async fn master_account(transport: &impl Transport, owner: Address) -> Result<Option<OwnedAccount>, ApiError> {
Ok(accounts_by_l1_address(transport, owner, None).await?.master().cloned())
}
#[cfg(test)]
#[path = "tests/owned.rs"]
mod tests;