Skip to main content

hypercall_admin/
state.rs

1//! Narrow application state for the admin/operator HTTP surface.
2//!
3//! [`AdminState`] holds exactly the subset of the runtime that admin handlers
4//! touch, and every field type resolves from a stable lower crate
5//! (`hypercall-runtime-api`, `hypercall-competition`, `hypercall-types`,
6//! `hypercall-db`, `hypercall-signer`, `hypercall-vol-oracle`, tokio). It deliberately does NOT reference any
7//! `hypercall-api` internal type, so this module (together with the rest of
8//! `admin/`) can be `git mv`'d into a standalone `hypercall-admin` crate with no
9//! rewiring.
10
11use std::sync::atomic::AtomicBool;
12use std::sync::Arc;
13
14use tokio::sync::{mpsc, oneshot, Mutex, Notify, RwLock};
15
16use hypercall_runtime_api::boundary::engine::{EngineJournalReader, EngineStateDigestProvider};
17use hypercall_runtime_api::boundary::market_inputs::{GreeksCacheReader, InstrumentsCacheReader};
18use hypercall_runtime_api::boundary::read_models::{
19    EngineBalanceSnapshotProvider, PortfolioCacheApi, TierCacheApi,
20};
21
22use hypercall_runtime_api::recovery_safety::SharedRecoverySafetyReport;
23use hypercall_runtime_api::trading_halt::TradingHaltState;
24use hypercall_runtime_api::{
25    ApiAsyncDb, ApiSyncDb, BuildInfo, EngineQuiesceRequest, QuoteProvider, QuoteProviderCache,
26    StandbyPromoter, UnifiedEngineRequest,
27};
28use hypercall_types::WalletAddress;
29
30/// Runtime configuration values the admin surface needs.
31///
32/// This mirrors the subset of `AppRuntimeConfig` used by admin endpoints and is
33/// defined locally (over stable primitives) so admin does not depend on the api
34/// `AppRuntimeConfig` type.
35#[derive(Clone)]
36pub struct AdminRuntimeConfig {
37    pub wal_path: std::path::PathBuf,
38    pub db_host: String,
39    pub db_name: String,
40    /// True when the runtime is in testnet mode. Gates the testnet-only admin
41    /// endpoints (which additionally require the `test-endpoints` feature).
42    pub testnet_mode: bool,
43    pub portfolio_margin_pool_enabled: bool,
44    pub portfolio_margin_settlement_allowlist: Vec<WalletAddress>,
45}
46
47/// Narrow operator/admin state.
48#[derive(Clone)]
49pub struct AdminState {
50    // Persistence ports.
51    pub db: Arc<dyn ApiAsyncDb>,
52    pub sync_db: Option<Arc<dyn ApiSyncDb>>,
53
54    // Read-model + market-input ports.
55    pub portfolio_cache: Arc<dyn PortfolioCacheApi>,
56    pub greeks_cache: Arc<dyn GreeksCacheReader>,
57    pub quote_provider: Arc<dyn QuoteProvider>,
58    pub tier_cache: Arc<dyn TierCacheApi>,
59    pub risk_vol_oracle: hypercall_vol_oracle::SharedVolOracle,
60    pub instruments_cache: Arc<dyn InstrumentsCacheReader>,
61    pub engine_state_digest_provider: Arc<dyn EngineStateDigestProvider>,
62    pub engine_journal_reader: Option<Arc<dyn EngineJournalReader>>,
63    pub balance_snapshot_provider: Arc<dyn EngineBalanceSnapshotProvider>,
64    pub recovery_safety_report: SharedRecoverySafetyReport,
65
66    // Quote-provider admin cache (RFQ register/list/suspend).
67    pub qp_cache: Arc<QuoteProviderCache>,
68
69    // Competition business logic (create/update/delete competitions).
70    pub competition: Arc<hypercall_competition::CompetitionService>,
71
72    // Engine command channels + signer.
73    pub order_sender: mpsc::Sender<UnifiedEngineRequest>,
74    pub engine_quiesce_sender: mpsc::Sender<EngineQuiesceRequest>,
75    pub tier_update_sender: Option<mpsc::Sender<hypercall_runtime_api::TierUpdateRequest>>,
76    pub pm_settlement_admin_sender:
77        Option<mpsc::Sender<hypercall_runtime_api::PmSettlementAdminRequest>>,
78    pub agent_auth_sender: mpsc::Sender<hypercall_runtime_api::AgentAuthRequest>,
79    pub rsm_signer: Option<Arc<dyn hypercall_signer::RsmSigner>>,
80
81    // Lifecycle handles.
82    pub trading_halt: Arc<RwLock<TradingHaltState>>,
83    pub standby_promote: Option<Arc<Mutex<Option<oneshot::Sender<()>>>>>,
84    pub standby_controller: Option<Arc<dyn StandbyPromoter>>,
85    pub drain_signal: Arc<Notify>,
86    pub is_draining: Arc<AtomicBool>,
87
88    // Metadata.
89    pub build_info: BuildInfo,
90    pub runtime_config: Arc<AdminRuntimeConfig>,
91    pub server_started_at: chrono::DateTime<chrono::Utc>,
92    pub boot_id: String,
93    pub admin_api_key: Option<String>,
94    pub allow_unauthenticated_monitoring: bool,
95}
96
97pub(crate) const ENGINE_RESPONSE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
98
99impl AdminState {
100    /// Submit a tier-update command through the engine and await acknowledgement.
101    ///
102    /// Mirrors the helper that previously lived on `AppState`.
103    pub async fn submit_tier_update_command(
104        &self,
105        wallet: hypercall_types::WalletAddress,
106    ) -> Result<(), String> {
107        let sender = self
108            .tier_update_sender
109            .as_ref()
110            .ok_or_else(|| "tier update engine channel is not configured".to_string())?;
111
112        let tier = self
113            .tier_cache
114            .get_tier(&wallet)
115            .await
116            .map(|tier| tier.tier)
117            .ok_or_else(|| format!("missing tier for tier update: wallet={wallet}"))?;
118        let margin_mode = self
119            .tier_cache
120            .get_margin_mode(&wallet)
121            .await
122            .map_err(|e| format!("failed to get margin mode for tier update: {}", e))?;
123        let trading_limits = self.tier_cache.get_trading_limits_async(&wallet).await;
124        let (tx, rx) = oneshot::channel();
125
126        sender
127            .send(hypercall_runtime_api::TierUpdateRequest {
128                wallet,
129                margin_mode,
130                tier,
131                trading_limits,
132                timestamp_ms: hypercall_types::utils::get_timestamp_millis(),
133                applied_tx: Some(tx),
134            })
135            .await
136            .map_err(|_| "tier update engine channel closed".to_string())?;
137
138        tokio::time::timeout(ENGINE_RESPONSE_TIMEOUT, rx)
139            .await
140            .map_err(|_| "tier update apply acknowledgement timed out".to_string())?
141            .map_err(|_| "tier update apply acknowledgement dropped".to_string())?
142    }
143}