hypercall_api/
asyncapi.rs1use asyncapi_rust::AsyncApi;
10
11pub use hypercall_types::ws_protocol::{
13 WsCandleUpdate, WsCompetitionFinalStanding, WsCompetitionFinalStats, WsCompetitionGapUpdate,
14 WsCompetitionRankChange, WsCompetitionUpdate, WsFillUpdate, WsIndexPriceUpdate,
15 WsIndicativeMarketData, WsLiquidationStateChange, WsMarketUpdate, WsMessage, WsOrderbookUpdate,
16 WsPositionExpired, WsRfqQuoteEntry, WsRfqQuotes, WsRfqStatusUpdate, WsTradeUpdate,
17};
18
19#[derive(AsyncApi)]
24#[asyncapi(
25 title = "Hypercall WebSocket API",
26 version = "0.0.1",
27 description = "Real-time data streaming for options trading.\n\n\
28 ## Connection\n\n\
29 Connect to `wss://HOST/ws` with optional query parameters:\n\
30 - `wallet`: Your wallet address (required for authenticated channels)\n\n\
31 ## Available Channels\n\n\
32 | Channel | Auth Required | Description |\n\
33 |---------|---------------|-------------|\n\
34 | `orderbook` | No | L2 orderbook updates for all symbols |\n\
35 | `options_chain` | No | Incremental options chain updates |\n\
36 | `trades` | No | Public trade feed |\n\
37 | `market_updates` | No | Market listing changes (created/deleted/expired) |\n\
38 | `candles:<UNDERLYING>:<RESOLUTION>` | No | Realtime underlying candle updates |\n\
39 | `index_prices` | No | Batch index/spot price updates for all underlyings |\n\
40 | `order_updates` | Yes | Your order status changes |\n\
41 | `fills` | Yes | Your trade fills |\n\
42 | `portfolio` | Yes | Your position and balance updates |\n\
43 | `liquidation` | Yes | Your liquidation state changes |\n\
44 | `competition` | Yes | Your competition rank and final stats |\n\
45 | `competition_engagement` | Yes | Your rank jumps, gap-to-next, and final standing updates |\n\
46 | `indicative_market_data` | No | Aggregated indicative quotes from quote providers |\n\
47 | `rfq` | Yes | RFQ quotes and status updates |\n\n\
48 Orderbook channel sizes (`bids`/`asks`) are human-readable contract quantities.\n\n\
49 ## Authentication\n\n\
50 Authenticated channels require the `wallet` query parameter and filter messages to only \
51 show data for that wallet. No signature is required for WebSocket connections.\n\n\
52 ## Message Format\n\n\
53 All messages are JSON with a `type` field indicating the message type."
54)]
55#[asyncapi_server(
56 name = "local",
57 host = "localhost:3000",
58 protocol = "ws",
59 pathname = "/ws",
60 description = "Local development server"
61)]
62#[asyncapi_server(
63 name = "testnet",
64 host = "testnet.hypercall.xyz",
65 protocol = "wss",
66 description = "Testnet environment"
67)]
68#[asyncapi_channel(
69 name = "websocket",
70 address = "/ws",
71 description = "Main WebSocket endpoint for subscribing to real-time data channels.",
72 parameter(
73 name = "wallet",
74 description = "Wallet address for authenticated channels (optional)",
75 schema_type = "string"
76 )
77)]
78#[asyncapi_messages(WsMessage)]
79pub struct WebSocketApi;
80
81pub fn asyncapi_spec_json() -> String {
83 let spec = WebSocketApi::asyncapi_spec();
84 sonic_rs::to_string_pretty(&spec).expect("Failed to serialize AsyncAPI spec")
85}
86
87pub fn asyncapi_spec_with_server(base_url: &str, description: &str) -> String {
89 let mut spec = WebSocketApi::asyncapi_spec();
90
91 let (protocol, host) = if base_url.starts_with("https://") {
93 ("wss", base_url.trim_start_matches("https://"))
94 } else if base_url.starts_with("http://") {
95 ("ws", base_url.trim_start_matches("http://"))
96 } else {
97 ("wss", base_url)
98 };
99
100 let server = asyncapi_rust::Server {
102 host: host.to_string(),
103 protocol: protocol.to_string(),
104 pathname: Some("/ws".to_string()),
105 description: Some(description.to_string()),
106 variables: None,
107 };
108
109 let mut servers = std::collections::HashMap::new();
110 servers.insert("production".to_string(), server);
111 spec.servers = Some(servers);
112
113 sonic_rs::to_string_pretty(&spec).expect("Failed to serialize AsyncAPI spec")
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn test_asyncapi_spec_generation() {
122 let json = asyncapi_spec_json();
123 assert!(json.contains("Hypercall WebSocket API"));
124 assert!(json.contains("orderbook"));
125 assert!(json.contains("competition"));
126 assert!(json.contains("competition_engagement"));
127 assert!(json.contains("CandleUpdate"));
128 assert!(json.contains("candles:<UNDERLYING>:<RESOLUTION>"));
129 }
130}