use crate::client::request::{body, number, user};
use crate::client::{ApiError, Transport, info};
use crate::utils::{Address, Dec, Json};
use serde::Deserialize;
pub use crate::account::fill::{Fill, FillLiquidation, TwapSliceFill};
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UserFees {
#[serde(default)]
pub daily_user_vlm: Vec<DailyVolume>,
pub fee_schedule: FeeSchedule,
pub user_cross_rate: Dec,
pub user_add_rate: Dec,
pub user_spot_cross_rate: Dec,
pub user_spot_add_rate: Dec,
pub active_referral_discount: Dec,
#[serde(default)]
pub active_staking_discount: Option<StakingDiscount>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DailyVolume {
pub date: String,
pub user_cross: Dec,
pub user_add: Dec,
pub exchange: Dec,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FeeSchedule {
pub cross: Dec,
pub add: Dec,
pub spot_cross: Dec,
pub spot_add: Dec,
pub tiers: FeeTiers,
pub referral_discount: Dec,
#[serde(default)]
pub staking_discount_tiers: Vec<StakingDiscount>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct FeeTiers {
pub vip: Vec<VolumeTier>,
pub mm: Vec<MakerTier>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VolumeTier {
pub ntl_cutoff: Dec,
pub cross: Dec,
pub add: Dec,
pub spot_cross: Dec,
pub spot_add: Dec,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MakerTier {
pub maker_fraction_cutoff: Dec,
pub add: Dec,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StakingDiscount {
pub bps_of_max_supply: Dec,
pub discount: Dec,
}
fn aggregated(aggregate: bool) -> Option<(&'static str, Json)> {
aggregate.then_some(("aggregateByTime", Json::Bool(true)))
}
#[must_use]
pub fn user_fills_request(account: Address, aggregate: bool) -> String {
body("userFills", [user(account)].into_iter().chain(aggregated(aggregate)))
}
#[must_use]
pub fn user_fills_by_time_request(account: Address, start: u64, end: Option<u64>, aggregate: bool) -> String {
let window = [Some(("startTime", number(start))), end.map(|end| ("endTime", number(end)))];
body(
"userFillsByTime",
[user(account)].into_iter().chain(window.into_iter().flatten()).chain(aggregated(aggregate)),
)
}
#[must_use]
pub fn user_twap_slice_fills_request(account: Address) -> String {
body("userTwapSliceFills", [user(account)])
}
#[must_use]
pub fn user_fees_request(account: Address) -> String {
body("userFees", [user(account)])
}
pub async fn user_fills(transport: &impl Transport, account: Address, aggregate: bool) -> Result<Vec<Fill>, ApiError> {
info(transport, user_fills_request(account, aggregate)).await
}
pub async fn user_fills_by_time(
transport: &impl Transport,
account: Address,
start: u64,
end: Option<u64>,
aggregate: bool,
) -> Result<Vec<Fill>, ApiError> {
info(transport, user_fills_by_time_request(account, start, end, aggregate)).await
}
pub async fn user_twap_slice_fills(
transport: &impl Transport,
account: Address,
) -> Result<Vec<TwapSliceFill>, ApiError> {
info(transport, user_twap_slice_fills_request(account)).await
}
pub async fn user_fees(transport: &impl Transport, account: Address) -> Result<UserFees, ApiError> {
info(transport, user_fees_request(account)).await
}
#[cfg(test)]
#[path = "tests/fills.rs"]
mod tests;