use serde::Deserialize;
use crate::client::{ApiError, Transport, info, info_request, user_request};
use crate::markets::Side;
use crate::utils::{Address, Dec, Json};
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Fill {
pub coin: String,
pub px: Dec,
pub sz: Dec,
pub side: Side,
pub time: u64,
pub start_position: Dec,
pub dir: String,
pub closed_pnl: Dec,
pub hash: String,
pub oid: u64,
pub crossed: bool,
pub fee: Dec,
pub tid: u64,
#[serde(default)]
pub cloid: Option<String>,
pub fee_token: String,
#[serde(default)]
pub builder_fee: Option<Dec>,
#[serde(default)]
pub liquidation: Option<FillLiquidation>,
#[serde(default)]
pub twap_id: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FillLiquidation {
#[serde(default)]
pub liquidated_user: Option<Address>,
pub mark_px: Dec,
pub method: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TwapSliceFill {
pub fill: Fill,
pub twap_id: u64,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UserFees {
#[serde(default)]
pub daily_user_vlm: Vec<DailyVolume>,
#[serde(default)]
pub fee_schedule: FeeSchedule,
#[serde(default)]
pub user_cross_rate: Dec,
#[serde(default)]
pub user_add_rate: Dec,
#[serde(default)]
pub user_spot_cross_rate: Dec,
#[serde(default)]
pub user_spot_add_rate: Dec,
#[serde(default)]
pub active_referral_discount: Dec,
#[serde(default)]
pub active_staking_discount: Option<StakingDiscount>,
}
impl UserFees {
#[must_use]
pub fn volume(&self) -> Option<Dec> {
Dec::checked_sum(self.daily_user_vlm.iter().flat_map(|day| [day.user_cross, day.user_add]))
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DailyVolume {
pub date: String,
#[serde(default)]
pub user_cross: Dec,
#[serde(default)]
pub user_add: Dec,
#[serde(default)]
pub exchange: Dec,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FeeSchedule {
#[serde(default)]
pub cross: Dec,
#[serde(default)]
pub add: Dec,
#[serde(default)]
pub spot_cross: Dec,
#[serde(default)]
pub spot_add: Dec,
#[serde(default)]
pub tiers: FeeTiers,
#[serde(default)]
pub referral_discount: Dec,
#[serde(default)]
pub staking_discount_tiers: Vec<StakingDiscount>,
}
#[derive(Debug, Clone, Default, 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, Default, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StakingDiscount {
#[serde(default)]
pub bps_of_max_supply: Dec,
#[serde(default)]
pub discount: Dec,
}
fn user(account: Address) -> (&'static str, Json) {
("user", Json::String(account.to_hex()))
}
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 {
let fields: Vec<_> = [user(account)].into_iter().chain(aggregated(aggregate)).collect();
info_request("userFills", &fields)
}
#[must_use]
pub fn user_fills_by_time_request(account: Address, start: u64, end: Option<u64>, aggregate: bool) -> String {
let number = |value: u64| Json::Number(value.to_string());
let window = [Some(("startTime", number(start))), end.map(|end| ("endTime", number(end)))];
let fields: Vec<_> =
[user(account)].into_iter().chain(window.into_iter().flatten()).chain(aggregated(aggregate)).collect();
info_request("userFillsByTime", &fields)
}
#[must_use]
pub fn user_twap_slice_fills_request(account: Address) -> String {
user_request("userTwapSliceFills", account)
}
#[must_use]
pub fn user_fees_request(account: Address) -> String {
user_request("userFees", 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;