mod parse;
mod wire;
use crate::markets::Interval;
use crate::utils::Address;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Subscription {
AllMids {
dex: String,
},
Notification {
user: Address,
},
WebData3 {
user: Address,
},
TwapStates {
user: Address,
dex: String,
},
ClearinghouseState {
user: Address,
dex: String,
},
OpenOrders {
user: Address,
dex: String,
},
Candle {
coin: String,
interval: Interval,
},
L2Book {
coin: String,
n_sig_figs: Option<u8>,
mantissa: Option<u8>,
fast: bool,
},
Trades {
coin: String,
},
OrderUpdates {
user: Address,
},
UserEvents {
user: Address,
},
UserFills {
user: Address,
aggregate_by_time: bool,
},
UserFundings {
user: Address,
},
UserNonFundingLedgerUpdates {
user: Address,
},
ActiveAssetCtx {
coin: String,
},
ActiveAssetData {
user: Address,
coin: String,
},
UserTwapSliceFills {
user: Address,
},
UserTwapHistory {
user: Address,
},
Bbo {
coin: String,
},
SpotState {
user: Address,
ignore_portfolio_margin: bool,
},
AllDexsClearinghouseState {
user: Address,
},
AllDexsAssetCtxs,
OutcomeMetaUpdates,
FastAssetCtxs,
AssetCtxs {
dex: String,
},
SpotAssetCtxs,
UserHistoricalOrders {
user: Address,
},
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum BadSubscription {
#[error("a subscription is an object")]
NotAnObject,
#[error("`{0}` is not a subscription the venue serves")]
UnknownType(String),
#[error("`{kind}` needs `{field}`")]
Missing { kind: &'static str, field: &'static str },
#[error("`{field}` of `{kind}` must be {expected}")]
Wrong { kind: &'static str, field: &'static str, expected: &'static str },
#[error("a coin is never an empty string")]
EmptyCoin,
#[error("nSigFigs is 2, 3, 4 or 5, not {0}")]
SigFigs(u8),
#[error("mantissa is 1, 2 or 5, and only with nSigFigs 5; {0} is refused here")]
Mantissa(u8),
}
impl Subscription {
#[must_use]
pub const fn kind(&self) -> &'static str {
match self {
Subscription::AllMids { .. } => "allMids",
Subscription::Notification { .. } => "notification",
Subscription::WebData3 { .. } => "webData3",
Subscription::TwapStates { .. } => "twapStates",
Subscription::ClearinghouseState { .. } => "clearinghouseState",
Subscription::OpenOrders { .. } => "openOrders",
Subscription::Candle { .. } => "candle",
Subscription::L2Book { .. } => "l2Book",
Subscription::Trades { .. } => "trades",
Subscription::OrderUpdates { .. } => "orderUpdates",
Subscription::UserEvents { .. } => "userEvents",
Subscription::UserFills { .. } => "userFills",
Subscription::UserFundings { .. } => "userFundings",
Subscription::UserNonFundingLedgerUpdates { .. } => "userNonFundingLedgerUpdates",
Subscription::ActiveAssetCtx { .. } => "activeAssetCtx",
Subscription::ActiveAssetData { .. } => "activeAssetData",
Subscription::UserTwapSliceFills { .. } => "userTwapSliceFills",
Subscription::UserTwapHistory { .. } => "userTwapHistory",
Subscription::Bbo { .. } => "bbo",
Subscription::SpotState { .. } => "spotState",
Subscription::AllDexsClearinghouseState { .. } => "allDexsClearinghouseState",
Subscription::AllDexsAssetCtxs => "allDexsAssetCtxs",
Subscription::OutcomeMetaUpdates => "outcomeMetaUpdates",
Subscription::FastAssetCtxs => "fastAssetCtxs",
Subscription::AssetCtxs { .. } => "assetCtxs",
Subscription::SpotAssetCtxs => "spotAssetCtxs",
Subscription::UserHistoricalOrders { .. } => "userHistoricalOrders",
}
}
#[must_use]
pub fn channel(&self) -> &'static str {
match self {
Subscription::UserEvents { .. } => "user",
Subscription::ActiveAssetCtx { coin } if is_spot(coin) => "activeSpotAssetCtx",
other => other.kind(),
}
}
#[must_use]
pub const fn user(&self) -> Option<Address> {
match self {
Subscription::Notification { user }
| Subscription::WebData3 { user }
| Subscription::TwapStates { user, .. }
| Subscription::ClearinghouseState { user, .. }
| Subscription::OpenOrders { user, .. }
| Subscription::OrderUpdates { user }
| Subscription::UserEvents { user }
| Subscription::UserFills { user, .. }
| Subscription::UserFundings { user }
| Subscription::UserNonFundingLedgerUpdates { user }
| Subscription::ActiveAssetData { user, .. }
| Subscription::UserTwapSliceFills { user }
| Subscription::UserTwapHistory { user }
| Subscription::SpotState { user, .. }
| Subscription::AllDexsClearinghouseState { user }
| Subscription::UserHistoricalOrders { user } => Some(*user),
_ => None,
}
}
#[must_use]
pub fn coin(&self) -> Option<&str> {
match self {
Subscription::Candle { coin, .. }
| Subscription::L2Book { coin, .. }
| Subscription::Trades { coin }
| Subscription::ActiveAssetCtx { coin }
| Subscription::ActiveAssetData { coin, .. }
| Subscription::Bbo { coin } => Some(coin),
_ => None,
}
}
pub fn check(&self) -> Result<(), BadSubscription> {
if self.coin().is_some_and(str::is_empty) {
return Err(BadSubscription::EmptyCoin);
}
if let Subscription::L2Book { n_sig_figs, mantissa, .. } = self {
if let Some(figures) = n_sig_figs.filter(|figures| !(2..=5).contains(figures)) {
return Err(BadSubscription::SigFigs(figures));
}
if let Some(mantissa) = mantissa.filter(|mantissa| *n_sig_figs != Some(5) || ![1, 2, 5].contains(mantissa))
{
return Err(BadSubscription::Mantissa(mantissa));
}
}
Ok(())
}
}
fn is_spot(coin: &str) -> bool {
coin.starts_with(['@', '#', '+']) || coin.contains('/')
}
#[cfg(test)]
#[path = "tests/fixtures.rs"]
mod fixtures;
#[cfg(test)]
#[path = "tests/subscription.rs"]
mod tests;