Oracle methodology
Two prices: oracle vs mark
MNX maintains two distinct prices for every market, and they answer two different questions.
The oracle price is external truth.It answers: what is this asset worth in the outside world? A price-updater job runs every minute, fetching prices from each market's configured external connectors — Pyth Lazer feeds for tokenized equities (including HKD-denominated listings converted through a mandatory USD/HKD foreign-exchange leg), Hyperliquid dex oracle prices, Massive-sourced stock aggregators, or the SemiAnalysis index for H100 markets. With three or more sources, prices deviating more than 5% from the median are dropped as outliers, and the survivors are averaged. The result is written on-chain via setPrice to the market's BackendPriceOracle, with each update step-bounded by a maxUpdateBps limit so a single bad print cannot move the oracle arbitrarily far in one write. Pyth-primary markets are moving to a hardened variant, PythAnchoredOracle, which verifies Pyth Lazer's signature inside the contract and applies the same step-bounding on-chain — the updater relays signed bytes it cannot forge, and the contract walks its answer toward the signed price. The oracle price drives margin checks, liquidation eligibility, the tradable price band, and the settlement reference. When a Pyth market session is closed (e.g. overnight for equities), the updater first tries the market's optional Hyperliquid after-hours fallback; absent one, it stops writing and lets the oracle age out under the freshness gate (below) rather than trusting a frozen price indefinitely.
The mark price is internal reality.It answers: where is this asset actually trading on MNX right now? Following Hyperliquid's methodology (hyperliquid_mark_v1), it is the median of components built from MNX's own order book: the oracle price plus a 150-second exponential moving average (EMA) of the internal-mid-minus-oracle basis, the median of best bid / best ask / last trade, and a 30-second EMA of that internal median. An EMA weights recent prices more heavily while damping sudden moves. When the book is too thin to form at least two components, the mark falls back to the oracle price (oracle_fallback_v1). The mark price drives unrealized profit and loss.
Why two prices? Separation is manipulation resistance. If liquidations keyed off the internal book alone, a trader could push a thin book to force liquidations; instead, liquidation eligibility runs off the externally-anchored oracle, gated by per-connector staleness limits (roughly three minutes for equity and Pyth feeds) so a stale feed halts liquidation planning rather than triggering it. Conversely, profit and loss tracks the mark so it reflects where you could actually trade on MNX, smoothed by exponential moving averages against momentary spikes.
One exception: valuation futures have no external feed at all. Their oracle is an eight-hour EMA of MNX's own order-book mid (futures_ema_8h_v1), falling back to last trade when no two-sided quote exists.
Price sources
MNX draws each market's oracle price from a source hierarchy that depends on the instrument type. The price-updater job runs once a minute, fetches external prices through each market's configured connector, and writes an outlier-filtered average of the available sources on-chain via setPrice, step-bounded by maxUpdateBps.
Equities — Pyth Lazer primary.For most listed equities, Pyth Lazer is the primary external oracle. The updater reads the connector's Lazer feed (and, for HKD-denominated names, a second Lazer foreign-exchange leg to convert into USD, where both legs are mandatory). When a Lazer feed reports its market session as closed, the fetch returns a PYTH_SESSION_CLOSED sentinel rather than a price. That sentinel propagates up through the connector fetch. Where a Hyperliquid after-hours secondary is configured (see below), the updater falls back to it; otherwise it skips the write for that cycle and the oracle holds its last value and ages out under the freshness gate, instead of publishing a stale or zero value.
Hyperliquid secondary fallback.Reviewed Hyperliquid secondaries cover windows when a primary Pyth or Massive equity source is unavailable. The fallback must remain inside its market's absolute floor and ceiling and within 20% of the last fresh primary-provider observation for U.S. equities (25% for the wider-session policy). That primary anchor is fixed while Hyperliquid is in use, preventing accepted fallback prices from gradually moving the reference. A missing anchor or one older than 14 days freezes the oracle; Hyperliquid never bootstraps the anchor.
Hyperliquid primary names. The STAR-market memory name CXMT is Hyperliquid-primary rather than fallback, because Pyth carries no feed for it — STAR-market and CXMT-class stocks are unavailable on Pyth for licensing reasons, so CXMT is sourced directly from Hyperliquid. The Hong Kong listings Z.AI (ZHIPU) and MINIMAX, by contrast, are Pyth-primary (Pyth Lazer feeds 3258 and 3257, HKD with an FX leg to USD); Hyperliquid serves only as their after-hours secondary.
Index and pre-IPO markets.The H100 GPU market takes its price from the SemiAnalysis GPU index rather than an equity feed. Pre-IPO valuation futures have no external price feed at all: before settlement, prices are set entirely by trading on MNX's internal order book, and the oracle price used for margin checks, liquidation eligibility, and the tradable price band is an eight-hour exponential moving average of the order-book mid, falling back to the last trade when no two-sided quote is available.
Across all types, once sources are gathered the oracle price is a simple average after non-positive values and greater-than-5% median outliers are dropped, then written on-chain with maxUpdateBps step-bounding so any single update moves the published price by at most the configured basis-point bound.
Liquidation and price staleness
Liquidation eligibility is evaluated off the oracle price. The liquidation planner reads the on-chain oracle price for each market (not the mark price) when determining margin health, liquidation eligibility, and the tradable price band. The auto-deleveraging (ADL) planner likewise consumes the tick-rounded oracle price when matching legs.
The oracle write is step-bounded by maxUpdateBps. Every on-chain setPricesubmission is checked against the oracle's per-update bound. If the target price would move the oracle by more than maxUpdateBps (a limit expressed in basis points, where one basis point is one hundredth of a percent) in a single update, the submitted price is stepped to the bound instead, and the bounded submission is recorded with method max_update_bounded_v1and an explicit reason ("bounded to … by maxUpdateBps=…"). A single anomalous external print therefore cannot instantly move liquidation thresholds or the tradable band; large moves are absorbed over successive one-minute updates.
On PythAnchoredOracle markets, the bound is enforced by the contract itself. Instead of a pre-stepped setPrice, the updater submits Pyth Lazer's raw signed update. The contract verifies the signature against the trusted Pyth signer, derives the price target and the market session from the signed payload, and clamps its own answer to at most maxUpdateBpsper write, moving toward that target — the same stepping math, relocated on-chain. While Pyth reports the session closed, the contract accepts the Hyperliquid secondary only alongside a fresh signed proof of the closed session, bands it against the signed closing print, and steps it under the same bound. Every accepted write must also carry a strictly newer Pyth payload timestamp than the last, so an already-consumed update can never be replayed. A compromised updater key can therefore delay the oracle, but it cannot forge a live price target or re-serve an old payload. During a signed closed session, it can choose a Hyperliquid target only inside the contract's band around the signed close, and each write remains capped by maxUpdateBps.
Every connector type carries a liquidation-price max-age gate. The freshness limits are:
| Connector | Max age for liquidation |
|---|---|
stock_aggregator (equities) | 3 minutes |
pyth (Pyth Lazer feeds) | 3 minutes |
| External equity feeds | 3 minutes |
semianalysis (H100 index) | 1 hour 15 minutes |
Markets may additionally set a per-market override (max_liquidation_price_age_ms).
Stale prices pause liquidations rather than trigger them. If a market's price exceeds its staleness gate, liquidation planning for that market is skipped entirely and eligibility checks fail with reasons such as oracle_price_stale. The system never liquidates against a number it cannot vouch for. This is also how a closed or stuck feed is made safe: when a Pyth session is closed (e.g. a Hong Kong equity outside trading hours) with no Hyperliquid fallback available, the updater stops writing, so the on-chain price ages past its staleness gate and liquidation pauses rather than running against an unrefreshed value. A stuck Hyperliquid feed — whether the after-hours fallback or a Hyperliquid-primary name — is handled the same way: the updater detects that the price has not changed and stops re-submitting it, so its on-chain timestamp ages out instead of looking perpetually fresh.
Auto-deleveraging (ADL) is the backstop. When an order-book liquidation cannot absorb a bankrupt account, the waterfall ADL planner takes over. It ranks counterparty positions by an ADL priority score (derived from profit-and-loss return and leverage used), then plans legs matching the bankrupt account against the highest-ranked counterparties at the tick-rounded, funding-adjusted oracle price. ADL applies to both user accounts and child vaults.
Trading sessions and fallback behavior
Equity and foreign-exchange markets priced from Pyth Lazer feeds do not trade around the clock. Each Pyth connector declares a funding_session (us_equity, provider_regular, or always) and a currency (USD or HKD; HKD markets carry a mandatory foreign-exchange leg that converts through the USD/HKD Lazer feed). When the price-updater job runs, it reads the Lazer equity feed's marketSession. If that session reports closed — for example, outside trading hours for the underlying equity — the Pyth fetch path does not return a stale or synthetic number. Instead it produces a distinct sentinel, PYTH_SESSION_CLOSED, which propagates unchanged out of the per-connector fetch and up into the main update loop. For an HKD market, the equity leg alone controls this routing. A closed foreign-exchange leg may still be used while its signed observation is fresh; once it exceeds its configured max age, the update holds instead of activating the Hyperliquid fallback.
When a market has no Hyperliquid secondary, the loop treats this sentinel as an instruction to hold. It logs Skipping <symbol>: Pyth session closed and moves to the next market without writing on-chain, so the oracle is frozen at its last submitted value for the duration of the closed session, rather than drifting or reverting. The same skip-and-hold applies when a connector returns no usable prices at all. Where a Hyperliquid secondary is configured, the fallback described below is attempted first, and the oracle only holds if that fallback is unavailable, stale, or out-of-bounds.
Where a market is configured with a Hyperliquid secondary, that venue provides the after-hours fallback. The Hyperliquid helper resolves the market's symbol, dex, and price scale, fetches the dex oracle price, applies its reviewed absolute floor and ceiling, and validates it against the fixed last fresh primary-provider observation. Hyperliquid does not publish a per-asset timestamp in this response, so freshness is the backend-observed time when the asset's price and order-book context last changed. An unchanged context fails closed after 10 minutes; this is not a Hyperliquid publication timestamp. U.S. equity fallbacks must stay within 20% of the anchor; the wider-session policy uses 25%. A price outside either guard leaves the oracle holding its last value. Missing and older-than-14-day anchors also fail closed, so a fallback can never bootstrap or refresh its own reference.
Every price the oracle does write is further constrained by maxUpdateBps, which step-bounds each on-chain update. If a target price would move more than the configured per-update ceiling, the submission is clamped to the bound, recorded with a bounded to ... by maxUpdateBps=... reason, and stored under the max_update_bounded_v1method. Funding across closed sessions follows each market's session calendar rather than the frozen oracle.