The shape of a configuration
Configuration is keyed byPriceKey, not by market:
AssetOracle:
PriceSource is one of four variants:
An Aquarius LP source must be the only source on its key. Composition through
Scaled.quote and LP leg keys is bounded at MAX_RESOLUTION_DEPTH = 3 (#229),
and a cycle is rejected with #225 OracleCycleDetected.
Providers
FeedSource { provider, decimals, max_stale_seconds }, where provider is:
Reflector references identify their asset with
OracleAssetRef::Stellar(Address)
or Symbol(Symbol); a String reference is rejected with #204. A Reflector
feed must be USD-quoted at listing (#220).
RedStone and Xoxno references carry a FeedNature of Market or Fundamental.
That choice changes how staleness is judged and whether the leg-age spread bound
applies — see the
oracle disruption runbook.
XOXNO oracle (RWA)
The XOXNO oracle is a self-hosted multi-signer oracle. Registered bot wallets submit prices; the contract stores the latest per signer per feed and recomputes a median aggregate under an N-of-M threshold, keeping reads O(1). Writes are threshold-signed and verify signer-set membership, submission age, and relative skew. Reads are open and Reflector-compatible. Its full callable surface — submitting prices, the Reflector-compatible reads, and the admin knobs — is on XOXNO oracle ABI. Use it when Reflector and RedStone have no suitable Stellar feed — especially tokenized RWA listings. A listing does not imply issuer endorsement, regulatory compliance, or user eligibility.
Keep submission age at or below the consuming oracle’s
max_price_stale_seconds. Below threshold, aggregates clear and reads fail
closed with adapter error 7 NoDataForFeed.
set_threshold, set_max_submission_age_seconds, and
set_max_relative_skew_seconds store the new bound only; they do not
re-derive aggregates that already exist. Follow a change with
recompute_feeds(feed_ids) in batches small enough to stay inside the
transaction footprint limit (roughly one ledger entry per signer plus three, per
feed). feeds() enumerates the registered ids.Mainnet RWA feeds
Feed ids must match the lending bot job pairs that submit prices.Composition and tolerance
A two-source price is the midpoint of its two legs, or it fails. There is no
band-by-band fallback, no “use the primary inside the first band”, and no
degrading to a single leg. One readable leg is an incomplete observation, not a
fallback price.
- Both legs must be readable and fresh. Only one leg producing a reading is
#205 UnsafePriceNotAllowed. within_tolerance_banddivides the larger leg by the smaller and compares that ratio toupper_ratio_bps. Beyond it,#205.- The served value is
(a + b) / 2.
lower_ratio_bps equals BPS × BPS / upper_ratio_bps,
rounded half up.
At price time only upper_ratio_bps is read. Dividing the larger leg by the
smaller already gives a ratio above 1 either way, so one bound covers both
directions.
upper_ratio_bps must be between MIN_TOLERANCE = 150 and
MAX_TOLERANCE = 2500 bps, else #208.
A single-source oracle serves its one leg directly, still subject to
staleness, positivity, and sanity checks — and is admitted only with a tighter
sanity band (MAX_SINGLE_SOURCE_SANITY_BAND_BPS = 1000, versus
MAX_LP_SANITY_BAND_BPS = 8182 for LP oracles). Exceeding it is
#226 SanityBandTooWideForSingleSource.
independence governs whether the two legs may depend on the same provider
contract. RequireDisjoint forbids it; AllowShared requires the declared set
to match the actual one exactly, else #232 IndependenceNotDeclared.
Staleness and sanity
- Staleness: a leg older than its own
max_stale_seconds, or a blend older than the oracle’smax_price_stale_seconds, is#206 PriceFeedStale. Both bounds are clamped to[60, 93_600]seconds. - Future-dated observations do not raise
#206. They are dropped outright, so the read fails as#210 NoLastPriceor#205instead. - Leg-age spread: when both legs are
Marketnature, they must be withinMAX_LEG_AGE_SPREAD_SECONDS = 3600of each other, because the blend weights them equally. AFundamentalleg is exempt. - Sanity bounds: the final WAD price must sit in
[min_sanity_price_wad, max_sanity_price_wad], else#223. The band is a pass/fail check on the already-blended value, never a clamp. It must be at leastMIN_SANITY_BAND_BPS = 50wide and at mostMAX_REASONABLE_PRICE_WAD = $1e9per whole token.
Fail-closed
Every valuation-dependent mutation consumes a complete price snapshot. Source failure, staleness, disagreement, or a failed sanity rule reverts the operation — the protocol halts risk-taking rather than acting on a questionable price. There is no permissive mode, no per-flow policy table, and no TWAP-to-spot fallback. Two read surfaces behave differently by design:get_market_indexes_detailed on the controller surfaces the same per-leg detail
for UIs: price_wad, primary_price_wad, anchor_price_wad, stale,
deviation, valid, and error_code.
Callable surface
Everything the price aggregator exposes. Reads are open; the three mutators are governance-routed.Reads
PriceFeedRaw is { price_wad: i128, asset_decimals: u32, timestamp: u64 }.
PriceStatus is { final_wad, primary_wad, secondary_wad, price_timestamp, stale, deviation, valid } — the blended price, both legs, the timestamp of the
older leg, and three flags.
price_spread is not a band around the blend. It returns the two source legs
sorted: (min(first, second), max(first, second)). On a single-source oracle
both entries come from the same leg. Use it to see how far your two sources
actually diverge, not as a confidence interval.prices and quotes by what you want on failure. A liquidation bot
sizing a position wants prices, so a bad feed aborts before it commits. A
dashboard wants quotes, so one broken market does not blank the whole page.
Operator controls
The price aggregator exposes three configuration mutators, all governance-routed:
All three publish
UpdateAssetOracleEvent (["config", "asset_oracle"]) with
the full post-change configuration.
Read the current configuration with oracle(key).
Next
Oracle disruption runbook
Triage, feed nature, blast radius, and repair when an asset stops pricing.
Markets
Listing lifecycle and hub-asset rows.
Spokes
Spiko and Centrifuge risk modules.
Security model
Fail-closed posture in the trust model.

