Skip to main content

hypercall_admin/
lib.rs

1//! Admin / operator HTTP surface (`hypercall-admin` crate).
2//!
3//! Every admin and operator endpoint is registered through [`admin_router`] over
4//! the narrow [`state::AdminState`]. These endpoints are protected by
5//! `ADMIN_API_KEY` and excluded from the public OpenAPI spec via the `Monitoring`
6//! and `Admin` hidden tags. The auth layer is NOT applied here; the server
7//! applies a single [`auth::monitoring_auth_middleware`] layer over this router,
8//! which is the entire admin surface.
9//!
10//! This crate depends only on stable lower crates (`hypercall-runtime-api`,
11//! `hypercall-types`, `hypercall-db`, ...) plus the admin-local
12//! [`state::AdminState`]. It does NOT depend on `hypercall-api`, and
13//! `hypercall-api` does NOT depend on it; they are siblings composed at the root.
14//!
15//! The competition admin handlers call `hypercall_competition::CompetitionService`
16//! directly; the business logic lives in that crate and the public competition
17//! read routes stay in `hypercall-api`.
18
19pub mod auth;
20pub mod competition;
21pub mod lifecycle;
22pub mod pm_settlement;
23pub mod rfq;
24pub mod state;
25#[cfg(feature = "test-endpoints")]
26pub mod testnet;
27
28/// Observability and operator endpoints served under the `/monitoring/*` and
29/// `/recovery-safety` URL prefixes. The submodule layout mirrors those routes.
30pub mod monitoring;
31
32use axum::{
33    routing::{delete, get, post, put},
34    Router,
35};
36
37use state::AdminState;
38
39// Bring the /monitoring/* handler modules into scope by short name so the
40// route table below reads the same as the URL paths it registers.
41use monitoring::{
42    accounts, engine, env, halts, instruments, integrity, journal, liquidation, oracles, outbox,
43    recovery, rpi, rsm, system, tiers,
44};
45
46/// Build the admin router over the narrow [`AdminState`].
47///
48/// Registers every AdminState-backed admin/operator route (the former
49/// `monitoring_routes()` plus the RFQ quote-provider and competition admin
50/// routes) and applies `.with_state(admin_state)`. It does NOT apply the auth
51/// layer; the server applies a single [`auth::monitoring_auth_middleware`]
52/// layer over this router.
53pub fn admin_router(admin_state: AdminState) -> Router {
54    // Routes that read/write engine + persistence state (AdminState).
55    let app_state_routes = Router::new()
56        .route("/monitoring/integrity", get(integrity::integrity_check))
57        .route("/monitoring/vol-oracles", get(oracles::vol_oracles))
58        .route("/monitoring/price-oracles", get(oracles::price_oracles))
59        .route("/monitoring/vol-surface", get(oracles::get_vol_surface))
60        .route(
61            "/monitoring/vol-surface/history",
62            get(oracles::get_vol_surface_history),
63        )
64        .route(
65            "/monitoring/vol-surface/symbol",
66            get(oracles::get_vol_surface_symbol),
67        )
68        .route(
69            "/monitoring/liquidation",
70            get(liquidation::liquidation_dashboard),
71        )
72        .route(
73            "/monitoring/directive-outbox",
74            get(outbox::directive_outbox),
75        )
76        .route(
77            "/monitoring/transaction-submitter",
78            get(outbox::transaction_submitter_detail),
79        )
80        .route("/monitoring/deposits", get(accounts::list_deposits))
81        .route("/monitoring/accounts", get(accounts::list_accounts))
82        .route("/monitoring/positions", get(accounts::list_positions))
83        .route(
84            "/monitoring/orderbooks",
85            get(integrity::orderbook_integrity),
86        )
87        .route(
88            "/monitoring/engine-state-digest",
89            get(engine::engine_state_digest),
90        )
91        .route(
92            "/monitoring/engine/balance-ledger/snapshot",
93            get(engine::balance_ledger_sync_snapshot),
94        )
95        // `/monitoring/recovery-safety` and `/recovery-safety` serve the same
96        // handler. The duplicate handler (`recovery_safety_monitoring`) was
97        // removed; both paths now share one handler so existing callers (chaos
98        // probe, blue-green test, dashboards) keep getting the JSON body.
99        .route(
100            "/monitoring/recovery-safety",
101            get(recovery::recovery_safety),
102        )
103        .route("/recovery-safety", get(recovery::recovery_safety))
104        .route(
105            "/recovery-safety/alert",
106            get(recovery::recovery_safety_alert),
107        )
108        // Durable journal endpoints
109        .route(
110            "/monitoring/engine/journal/commands",
111            get(journal::list_journal_commands),
112        )
113        .route(
114            "/monitoring/engine/journal/commands/:request_id",
115            get(journal::get_journal_command_by_id),
116        )
117        // Environment configuration endpoint
118        .route("/monitoring/env", get(env::get_monitoring_env))
119        // Instruments cache reload endpoint
120        .route(
121            "/monitoring/reload_instruments_cache",
122            post(instruments::reload_instruments_cache),
123        );
124
125    let app_state_routes = app_state_routes
126        // Trading halt kill-switch endpoints
127        .route("/monitoring/trading-halts", get(halts::get_trading_halts))
128        .route(
129            "/monitoring/trading-halts/global",
130            post(halts::set_global_trading_halt),
131        )
132        .route(
133            "/monitoring/trading-halts/market",
134            post(halts::set_market_trading_halt),
135        )
136        // Admin set-tier endpoint (used by MM self-registration)
137        .route("/monitoring/set-tier", post(tiers::set_tier))
138        // Memory stats endpoint (always available)
139        .route("/monitoring/memory", get(system::memory_stats))
140        .route("/monitoring/rpi", get(rpi::rpi_monitoring))
141        .route("/monitoring/rsm/status", get(rsm::get_rsm_status))
142        // Heap dump endpoint (requires --features heap-profiling)
143        .route("/monitoring/heap-dump", post(system::heap_dump))
144        .route("/admin/promote", post(lifecycle::admin_promote))
145        .route("/admin/drain", post(lifecycle::admin_drain))
146        .route("/admin/undrain", post(lifecycle::admin_undrain))
147        .route("/drain-status", get(lifecycle::drain_status))
148        // Portfolio-margin settlement pool admin routes.
149        .route(
150            "/admin/pm-settlement/pools",
151            get(pm_settlement::get_pm_settlement_pools),
152        )
153        .route(
154            "/admin/pm-settlement/accounts",
155            get(pm_settlement::get_pm_settlement_accounts),
156        )
157        .route(
158            "/admin/pm-settlement/events",
159            get(pm_settlement::get_pm_settlement_events),
160        )
161        .route(
162            "/admin/pm-settlement/interest-events",
163            get(pm_settlement::get_pm_settlement_interest_events),
164        )
165        .route(
166            "/admin/pm-settlement/repayment-events",
167            get(pm_settlement::get_pm_settlement_repayment_events),
168        )
169        .route(
170            "/admin/pm-settlement/recovery",
171            get(pm_settlement::get_pm_recovery_projections),
172        )
173        .route(
174            "/admin/pm-settlement/gate-state",
175            get(pm_settlement::get_pm_settlement_gate_state),
176        )
177        .route(
178            "/admin/pm-settlement/pool-config",
179            post(pm_settlement::set_pm_settlement_pool_config),
180        )
181        .route(
182            "/admin/pm-settlement/interest",
183            post(pm_settlement::accrue_pm_settlement_interest),
184        )
185        .route(
186            "/admin/pm-settlement/recovery/plan",
187            post(pm_settlement::journal_pm_recovery_plan),
188        )
189        .route(
190            "/admin/pm-settlement/recovery/action/submitted",
191            post(pm_settlement::mark_pm_recovery_action_submitted),
192        )
193        .route(
194            "/admin/pm-settlement/recovery/action/resolve",
195            post(pm_settlement::resolve_pm_recovery_action),
196        )
197        // RFQ quote-provider admin routes (operate on AdminState.qp_cache).
198        .route(
199            "/admin/rfq/quote-providers",
200            post(rfq::register_quote_provider),
201        )
202        .route("/admin/rfq/quote-providers", get(rfq::list_quote_providers))
203        .route(
204            "/admin/rfq/quote-providers/:wallet/suspend",
205            post(rfq::suspend_quote_provider),
206        )
207        // Competition admin routes (hypercall-competition business logic).
208        .route(
209            "/monitoring/competitions",
210            post(competition::create_competition),
211        )
212        .route(
213            "/monitoring/competitions/:id",
214            put(competition::update_competition),
215        )
216        .route(
217            "/monitoring/competitions/:id",
218            delete(competition::delete_competition),
219        );
220
221    // Testnet-only admin endpoints (compile-time gated; the handler also
222    // refuses to run unless the runtime is in testnet mode).
223    #[cfg(feature = "test-endpoints")]
224    let app_state_routes = app_state_routes.route(
225        "/monitoring/test/agent-authorization",
226        post(testnet::apply_agent_authorization),
227    );
228
229    app_state_routes.with_state(admin_state)
230}
231
232/// OpenAPI document for the admin/operator surface.
233///
234/// These paths and schemas were previously declared in `hypercall-api`'s
235/// `openapi.rs`. They live here now so the api spec no longer references admin
236/// internals directly; the exporter merges this document into the internal spec
237/// when `--include-hidden` is requested.
238#[derive(utoipa::OpenApi)]
239#[openapi(
240    paths(
241        crate::monitoring::integrity::integrity_check,
242        crate::monitoring::oracles::vol_oracles,
243        crate::monitoring::oracles::price_oracles,
244        crate::monitoring::oracles::get_vol_surface,
245        crate::monitoring::oracles::get_vol_surface_history,
246        crate::monitoring::oracles::get_vol_surface_symbol,
247        crate::monitoring::outbox::directive_outbox,
248        crate::monitoring::outbox::transaction_submitter_detail,
249        crate::monitoring::accounts::list_accounts,
250        crate::monitoring::accounts::list_positions,
251        crate::monitoring::integrity::orderbook_integrity,
252        crate::monitoring::engine::engine_state_digest,
253        crate::monitoring::recovery::recovery_safety,
254        crate::monitoring::recovery::recovery_safety_alert,
255        crate::monitoring::rpi::rpi_monitoring,
256        crate::monitoring::rsm::get_rsm_status,
257        crate::monitoring::halts::get_trading_halts,
258        crate::monitoring::halts::set_global_trading_halt,
259        crate::monitoring::halts::set_market_trading_halt,
260        crate::monitoring::tiers::set_tier,
261        crate::competition::create_competition,
262        crate::competition::update_competition,
263        crate::competition::delete_competition,
264    ),
265    components(schemas(
266        hypercall_signer::RsmSignerStatus,
267        crate::monitoring::integrity::CrossedOrderbook,
268        crate::monitoring::integrity::OrderbookIntegrityReport,
269        hypercall_runtime_api::boundary::engine::EngineStateDigest,
270        crate::monitoring::integrity::IntegrityReport,
271        crate::monitoring::integrity::IntegrityStatus,
272        crate::monitoring::integrity::IntegrityCheck,
273        crate::monitoring::integrity::LedgerSummary,
274        crate::monitoring::integrity::PositionSummary,
275        crate::monitoring::integrity::PositionImbalance,
276        crate::monitoring::integrity::SettlementSummary,
277        crate::monitoring::oracles::VolOracleStatusResponse,
278        crate::monitoring::oracles::VolOracleStatusesResponse,
279        crate::monitoring::oracles::PriceOracleStatusResponse,
280        crate::monitoring::oracles::PriceOracleStatusesResponse,
281        crate::monitoring::oracles::VolSurfaceApiResponse,
282        crate::monitoring::oracles::VolSurfaceHistoryResponse,
283        crate::monitoring::oracles::VolSurfaceHistoryPoint,
284        crate::monitoring::oracles::VolSurfaceSymbolResponse,
285        crate::monitoring::oracles::SymbolIvPoint,
286        crate::monitoring::rpi::RpiMonitoringQuery,
287        crate::monitoring::rpi::RpiMonitoringResponse,
288        crate::monitoring::rpi::RpiMonitoringBuildInfo,
289        hypercall_runtime_api::rpi_monitor::RpiMonitorEvent,
290        crate::monitoring::outbox::DirectiveOutboxQuery,
291        crate::monitoring::outbox::DirectiveOutboxRowResponse,
292        crate::monitoring::outbox::DirectiveOutboxResponse,
293        crate::monitoring::outbox::TransactionSubmitterQuery,
294        crate::monitoring::outbox::TransactionSubmitterAttemptResponse,
295        crate::monitoring::outbox::TransactionSubmitterDetailResponse,
296        hypercall_runtime_api::recovery_safety::RecoverySafetyReport,
297        hypercall_runtime_api::recovery_safety::RecoverySafetyMonitoringResponse,
298        hypercall_runtime_api::recovery_safety::RecoverySafetyBuildInfo,
299        hypercall_runtime_api::recovery_safety::RecoverySafetyStatus,
300        hypercall_runtime_api::recovery_safety::RecoverySafetyCheck,
301        hypercall_runtime_api::recovery_safety::RecoverySafetyAlertPoint,
302        hypercall_runtime_api::recovery_safety::RecoverySafetyCheckpoint,
303        hypercall_runtime_api::recovery_safety::RecoverySafetySnapshot,
304        hypercall_runtime_api::recovery_safety::RecoverySafetyReplayCoverage,
305        hypercall_runtime_api::recovery_safety::RecoverySafetyDrainMarker,
306        hypercall_runtime_api::recovery_safety::RecoverySafetyCash,
307        hypercall_vol_oracle::VolSurfaceSnapshot,
308        hypercall_vol_oracle::VolPoint,
309        hypercall_vol_oracle::DeltaIvExport,
310        hypercall_vol_oracle::DeltaCurveExport,
311        crate::monitoring::halts::TradingHaltsResponse,
312        crate::monitoring::halts::SetGlobalTradingHaltRequest,
313        crate::monitoring::halts::SetMarketTradingHaltRequest,
314        crate::monitoring::tiers::AdminSetTierRequest,
315        hypercall_runtime_api::trading_halt::TradingHaltActivation,
316        hypercall_runtime_api::trading_halt::TradingHaltScope,
317        hypercall_types::api_models::MonitoringAccountSummary,
318        hypercall_types::api_models::MonitoringAccountsResponse,
319        hypercall_types::api_models::MonitoringPositionHolder,
320        hypercall_types::api_models::MonitoringSymbolPosition,
321        hypercall_types::api_models::MonitoringPositionsResponse,
322        hypercall_types::api_models::CompetitionUpsertRequest,
323        hypercall_types::api_models::CompetitionUpdateRequest,
324        hypercall_types::api_models::CompetitionResponse,
325        hypercall_types::api_models::CompetitionData,
326    ))
327)]
328pub struct AdminApiDoc;
329
330#[cfg(feature = "test-endpoints")]
331#[derive(utoipa::OpenApi)]
332#[openapi(
333    paths(crate::testnet::apply_agent_authorization),
334    components(schemas(
335        crate::testnet::TestAgentAuthorizationRequest,
336        crate::testnet::TestAgentAuthorizationApiResponse,
337        crate::testnet::TestAgentAuthorizationResponse,
338    ))
339)]
340struct TestEndpointsApiDoc;
341
342/// Returns the admin OpenAPI document.
343pub fn admin_openapi() -> utoipa::openapi::OpenApi {
344    let spec = <AdminApiDoc as utoipa::OpenApi>::openapi();
345    #[cfg(feature = "test-endpoints")]
346    let spec = {
347        let mut spec = spec;
348        spec.merge(<TestEndpointsApiDoc as utoipa::OpenApi>::openapi());
349        spec
350    };
351    spec
352}