1pub mod boundary;
2pub mod db_ports;
3pub mod drain;
4pub mod error;
5pub mod quote_provider_cache;
6pub mod recovery_safety;
7pub mod rpi_monitor;
8pub mod sonic_json;
9pub mod trading_halt;
10pub mod valuation;
11
12pub use db_ports::{ApiAsyncDb, ApiRsmStateDb, ApiSyncDb};
13pub use quote_provider_cache::{QuoteProviderCache, QuoteProviderConfig};
14
15use alloy::primitives::{FixedBytes, U256};
16use alloy::sol_types::SolValue;
17use std::collections::HashMap;
18use std::sync::{
19 atomic::{AtomicI64, Ordering},
20 Arc,
21};
22use std::time::{Duration, Instant};
23
24use hypercall_types::{
25 api_models::TradingLimits,
26 directives::{CreditOption, SYSTEM_ACTION_ID_CREDIT_OPTION, SYSTEM_ACTION_VERSION},
27 MarketActionMessage, MarketUpdateMessage, OrderActionMessage, OrderUpdateMessage, Side,
28 WalletAddress,
29};
30use rust_decimal::Decimal;
31use tokio::sync::broadcast;
32use tokio::sync::{mpsc, oneshot};
33
34pub use hypercall_engine::command::{RfqExecuteCommand, RfqExecuteLeg, RfqExecuteResult};
35pub use hypercall_recovery::{
36 QuiesceAction as EngineQuiesceAction, QuiesceReport as EngineQuiesceReport,
37};
38
39static PENDING_ENGINE_REQUESTS: AtomicI64 = AtomicI64::new(0);
44
45pub fn increment_pending_requests() {
46 PENDING_ENGINE_REQUESTS.fetch_add(1, Ordering::Relaxed);
47}
48
49pub fn decrement_pending_requests() {
50 PENDING_ENGINE_REQUESTS.fetch_sub(1, Ordering::Relaxed);
51}
52
53pub fn get_pending_requests() -> i64 {
54 PENDING_ENGINE_REQUESTS.load(Ordering::Relaxed)
55}
56
57pub struct RfqExecuteRequest {
59 pub command: RfqExecuteCommand,
60 pub response_tx: oneshot::Sender<RfqExecuteResult>,
61}
62
63pub struct PmSettlementAdminRequest {
65 pub command: hypercall_engine::command::EngineCommand,
66 pub applied_tx: oneshot::Sender<Result<(), String>>,
67}
68
69#[async_trait::async_trait]
70pub trait EngineEventPersistence: Send + Sync {
71 async fn handle_event(&self, event: &hypercall_types::EngineMessage) -> anyhow::Result<()>;
72}
73
74#[derive(Clone, Debug)]
76pub struct SnapshotBookQuote {
77 pub best_bid: Option<f64>,
78 pub best_bid_size: Option<f64>,
79 pub best_ask: Option<f64>,
80 pub best_ask_size: Option<f64>,
81 pub mid: Option<f64>,
82 pub bids: Vec<(f64, f64)>,
83 pub asks: Vec<(f64, f64)>,
84}
85
86impl SnapshotBookQuote {
87 pub fn empty() -> Self {
90 Self {
91 best_bid: None,
92 best_bid_size: None,
93 best_ask: None,
94 best_ask_size: None,
95 mid: None,
96 bids: Vec::new(),
97 asks: Vec::new(),
98 }
99 }
100
101 pub fn is_empty_book(&self) -> bool {
102 self.best_bid.is_none()
103 && self.best_ask.is_none()
104 && self.bids.is_empty()
105 && self.asks.is_empty()
106 }
107}
108
109#[derive(Clone, Debug)]
115pub enum BookSnapshotState {
116 NotReady {
117 l2_seq: i64,
118 },
119 Ready {
120 quote: SnapshotBookQuote,
121 l2_seq: i64,
122 },
123}
124
125#[derive(Clone, Debug)]
126pub struct QuoteSnapshot {
127 pub quotes: HashMap<String, SnapshotBookQuote>,
128 pub l2_seq: i64,
129}
130
131pub trait QuoteProvider: Send + Sync {
133 fn get_quote(&self, symbol: &str) -> Option<SnapshotBookQuote>;
134 fn get_quote_with_seq(&self, symbol: &str) -> (Option<SnapshotBookQuote>, i64);
135 fn book_snapshot_state(&self, symbol: &str) -> BookSnapshotState;
136 fn all_quotes(&self) -> HashMap<String, SnapshotBookQuote>;
137 fn l2_seq(&self) -> i64;
138 fn snapshot(&self) -> QuoteSnapshot;
139 fn staleness(&self) -> Duration;
140}
141
142#[derive(Debug, Clone)]
144pub struct RuntimeOrderSummary {
145 pub order_id: u64,
146 pub symbol: String,
147 pub side: Side,
148 pub price: Decimal,
149 pub original_size: Decimal,
151 pub remaining_size: Decimal,
153 pub is_perp: bool,
154 pub mmp_enabled: bool,
155 pub client_id: Option<String>,
156 pub created_at: i64,
157}
158
159impl From<hypercall_engine::OrderSummary> for RuntimeOrderSummary {
160 fn from(order: hypercall_engine::OrderSummary) -> Self {
161 Self {
162 order_id: order.order_id,
163 symbol: order.symbol,
164 side: order.side,
165 price: order.price,
166 original_size: order.original_size,
167 remaining_size: order.remaining_size,
168 is_perp: order.is_perp,
169 mmp_enabled: order.mmp_enabled,
170 client_id: order.client_id,
171 created_at: order.created_at,
172 }
173 }
174}
175
176pub trait OrderSnapshotProvider: Send + Sync {
178 fn get_open_orders_for_wallet(&self, wallet: &WalletAddress) -> Vec<RuntimeOrderSummary>;
179 fn get_all_orders(&self) -> Vec<(RuntimeOrderSummary, WalletAddress)>;
180}
181
182pub trait AgentAuthProvider: Send + Sync {
184 fn is_agent_authorized(&self, wallet: &WalletAddress, agent: &WalletAddress) -> bool;
185 fn get_authorized_agents(&self, wallet: &WalletAddress) -> Vec<WalletAddress>;
186}
187
188#[derive(Debug, Clone, Copy, PartialEq, Eq)]
189pub struct SystemCreditOptionDirective {
190 pub underlying: [u8; 32],
191 pub expiry: U256,
192 pub strike: U256,
193 pub is_call: bool,
194 pub amount_wei: U256,
195}
196
197pub fn encode_credit_option_action(action: SystemCreditOptionDirective) -> anyhow::Result<Vec<u8>> {
198 if action.amount_wei.is_zero() {
199 anyhow::bail!("refusing to submit SystemCreditOption with amount_wei=0");
200 }
201 if action.expiry.is_zero() {
202 anyhow::bail!("refusing to submit SystemCreditOption with expiry=0");
203 }
204 if action.strike.is_zero() {
205 anyhow::bail!("refusing to submit SystemCreditOption with strike=0");
206 }
207 if action.underlying == [0u8; 32] {
208 anyhow::bail!("refusing to submit SystemCreditOption with zero underlying");
209 }
210
211 hypercall_signer::encode_action_bytes(
212 SYSTEM_ACTION_VERSION,
213 SYSTEM_ACTION_ID_CREDIT_OPTION,
214 &CreditOption {
215 underlying: FixedBytes::<32>::from(action.underlying),
216 expiry: action.expiry,
217 strike: action.strike,
218 isCall: action.is_call,
219 amountWei: action.amount_wei,
220 }
221 .abi_encode(),
222 )
223 .map_err(|error| anyhow::anyhow!("{}", error))
224}
225
226pub type ShutdownRx = broadcast::Receiver<()>;
230
231#[derive(Clone)]
236pub struct Shutdown {
237 tx: broadcast::Sender<()>,
238 triggered: Arc<std::sync::atomic::AtomicBool>,
239}
240
241impl Default for Shutdown {
242 fn default() -> Self {
243 Self::new()
244 }
245}
246
247impl Shutdown {
248 pub fn new() -> Self {
250 let (tx, _) = broadcast::channel(1);
251 Self {
252 tx,
253 triggered: Arc::new(std::sync::atomic::AtomicBool::new(false)),
254 }
255 }
256
257 pub fn tx(&self) -> broadcast::Sender<()> {
259 self.tx.clone()
260 }
261
262 pub fn subscribe(&self) -> ShutdownRx {
264 self.tx.subscribe()
265 }
266
267 pub fn trigger(&self) {
269 self.triggered
270 .store(true, std::sync::atomic::Ordering::SeqCst);
271 let _ = self.tx.send(());
272 }
273
274 pub fn is_triggered(&self) -> bool {
276 self.triggered.load(std::sync::atomic::Ordering::SeqCst)
277 }
278}
279
280#[derive(Debug, Clone)]
282pub struct UnifiedEngineRequest {
283 pub message: OrderActionMessage,
284 pub response_tx: mpsc::Sender<OrderUpdateMessage>,
285 pub enqueued_at: Instant,
287 #[cfg(feature = "otel-tracing")]
289 pub trace_context: Option<opentelemetry::Context>,
290}
291
292#[derive(Debug, Clone)]
294pub struct MarketRequest {
295 pub message: MarketActionMessage,
296 pub response_tx: mpsc::Sender<MarketUpdateMessage>,
297}
298
299#[derive(Debug)]
302pub struct AgentAuthRequest {
303 pub wallet: WalletAddress,
304 pub agent: WalletAddress,
305 pub approve: bool,
306 pub expires_at_ms: Option<u64>,
307 pub nonce: Option<u64>,
309 pub applied_tx: oneshot::Sender<Result<(), String>>,
310}
311
312#[derive(Debug)]
315pub struct NonceCheckRequest {
316 pub wallet: WalletAddress,
317 pub nonce: u64,
318 pub applied_tx: oneshot::Sender<Result<(), String>>,
319}
320
321#[derive(Debug)]
324pub struct MarginModeUpdateRequest {
325 pub wallet: WalletAddress,
326 pub margin_mode: hypercall_types::MarginMode,
327 pub timestamp_ms: u64,
328 pub applied_tx: oneshot::Sender<Result<(), String>>,
329}
330
331#[derive(Debug)]
333pub struct DepositRequest {
334 pub wallet: WalletAddress,
335 pub amount: Decimal,
336 pub timestamp_ms: u64,
337 pub sequence: Option<u64>,
339 pub source_event_hash: FixedBytes<32>,
340 pub journal_request_id: String,
343 pub outbox_appends: Vec<hypercall_db::DirectiveOutboxAppend>,
344 pub applied_tx: Option<oneshot::Sender<Result<(), String>>>,
345}
346
347#[derive(Debug)]
350pub struct OptionDepositRequest {
351 pub request_id: String,
354 pub wallet: WalletAddress,
355 pub symbol: String,
356 pub quantity: Decimal,
357 pub timestamp_ms: u64,
358 pub applied_tx: Option<oneshot::Sender<Result<(), String>>>,
359}
360
361#[derive(Debug)]
362pub struct OptionWithdrawalRequest {
363 pub request_id: String,
366 pub wallet: WalletAddress,
367 pub account: WalletAddress,
368 pub signer: WalletAddress,
369 pub rsm_signer: WalletAddress,
370 pub symbol: String,
371 pub quantity: Decimal,
372 pub nonce: u64,
373 pub action: Vec<u8>,
374 pub timestamp_ms: u64,
375 pub applied_tx: Option<oneshot::Sender<Result<OptionWithdrawalApplyReceipt, String>>>,
376}
377
378#[derive(Debug, Clone)]
379pub struct OptionWithdrawalApplyReceipt {
380 pub directive_id: String,
381 pub domain_status: hypercall_db::DirectiveDomainStatus,
382 pub delivery_status: hypercall_db::DirectiveDeliveryStatus,
383}
384
385#[derive(Debug)]
386pub struct CashWithdrawalRequest {
387 pub request_id: String,
390 pub wallet: WalletAddress,
391 pub account: WalletAddress,
392 pub destination: WalletAddress,
393 pub signer: WalletAddress,
394 pub rsm_signer: WalletAddress,
395 pub amount: Decimal,
396 pub amount_wei: u64,
397 pub nonce: u64,
398 pub timestamp_ms: u64,
399 pub applied_tx: Option<oneshot::Sender<Result<CashWithdrawalApplyReceipt, String>>>,
400}
401
402#[derive(Debug, Clone)]
403pub struct CashWithdrawalApplyReceipt {
404 pub directive_id: String,
405 pub domain_status: hypercall_db::DirectiveDomainStatus,
406 pub delivery_status: hypercall_db::DirectiveDeliveryStatus,
407 pub balance_after: Decimal,
408}
409
410#[derive(Debug)]
413pub struct LiquidationBonusRequest {
414 pub request_id: String,
417 pub wallet: WalletAddress,
418 pub amount: Decimal,
419 pub timestamp_ms: u64,
420 pub sequence: Option<u64>,
421 pub applied_tx: Option<oneshot::Sender<Result<(), String>>>,
422}
423
424#[derive(Debug)]
427pub struct TierUpdateRequest {
428 pub wallet: WalletAddress,
429 pub margin_mode: hypercall_types::MarginMode,
430 pub tier: String,
431 pub trading_limits: TradingLimits,
432 pub timestamp_ms: u64,
433 pub applied_tx: Option<oneshot::Sender<Result<(), String>>>,
434}
435
436#[derive(Debug)]
438pub struct HypercoreEquityRequest {
439 pub wallet: WalletAddress,
440 pub account_value: Decimal,
441 pub timestamp_ms: u64,
442}
443
444#[derive(Clone, Debug, PartialEq, Eq)]
446pub enum StandbyPromoteOutcome {
447 Promoted { queued_orders: usize },
448 AlreadyActive,
449}
450
451#[async_trait::async_trait]
453pub trait StandbyPromoter: Send + Sync {
454 async fn promote(&self) -> StandbyPromoteOutcome;
455}
456
457#[derive(Clone, Debug)]
459pub struct BuildInfo {
460 pub version: String,
461 pub commit: String,
462 pub git_ref: String,
463 pub build_time: String,
464}
465
466#[derive(Debug)]
468pub struct EngineQuiesceRequest {
469 pub action: EngineQuiesceAction,
470 pub response_tx: oneshot::Sender<EngineQuiesceReport>,
471}