10^18), and applies a failure policy chosen by the calling flow’s risk direction. The pool never sees a price.
Providers
| Provider | Role | Reads |
|---|---|---|
| Reflector (SEP-40) | Primary | Spot price, or a TWAP over up to MAX_TWAP_RECORDS = 12 records. |
| RedStone | Anchor | Spot price with dual timestamps (observed and published). |
Oracle strategy
Each market picks one resolution strategy (OracleStrategy):
Single— read the primary feed only. No cross-check.PrimaryWithAnchor— read the primary and an anchor, then reconcile them through tolerance bands (below). Every current mainnet market uses this.
OracleAssetRef: Stellar(Address) for an asset-address feed, Symbol(Symbol) for a symbol feed, or String(String).
Tolerance bands
UnderPrimaryWithAnchor, the controller measures how far the primary price has drifted from the anchor and decides which price to trust (tolerance.rs). It computes the ratio in BPS:
first_tolerance and last_tolerance:
| Deviation | Result |
|---|---|
| Within the first band | Use the primary price. |
| Between the first and last bands | Use the midpoint (anchor + primary) / 2. |
| Beyond the last band | Reject with #205 UnsafePriceNotAllowed under a strict policy; fall back to the anchor under a permissive policy. |
first_tolerance ∈ [50, 5000] BPS, last_tolerance ∈ [150, 5000] BPS, and last_tolerance > first_tolerance. On mainnet, USDC, XLM, EURC, BTC, and ETH use 200 / 500; USTRY uses 300 / 800; CETES and TESOURO use 400 / 1000. See Risk parameters.
Staleness and sanity
Beyond deviation, every price passes freshness and bounds checks (observation.rs):
- Staleness: a feed older than the market’s
max_price_stale_secondsis stale. That window is clamped to[60, 86_400]seconds; values outside the range are pulled to the nearest bound. - Future skew: a feed timestamped more than
60seconds ahead of the ledger clock (MAX_FUTURE_SKEW_SECONDS) is rejected unconditionally with#206 PriceFeedStale— no policy tolerates it. - Sanity bounds: the final WAD price must fall within the market’s
[min_sanity_price_wad, max_sanity_price_wad]. A value outside the band is a sanity violation, tolerated only by the lenient policies below.
Oracle policies
Every flow selects anOraclePolicy before reading prices (policy.rs). The policy decides which leniencies are acceptable for that flow’s risk direction. The strict policies — used by every flow that increases risk or moves collateral — reject all of them.
| Flow | Policy | Disabled market | Stale source | Unsafe deviation | Degraded dual source | Sanity violation |
|---|---|---|---|---|---|---|
| Borrow / withdraw-with-debt | RiskIncreasing | no | no | no | no | no |
| Liquidation | Liquidation | no | no | no | no | no |
| Supply / repay (active market) | RiskDecreasing | no | yes | yes | yes | yes |
| Repay on a disabled market | Repay | yes | yes | yes | yes | yes |
| Views | View | yes | yes | yes | yes | yes |
Liquidation deliberately mirrors RiskIncreasing (it rejects every loosening) so seizure accounting can never read a degraded price; the two are kept as distinct variants for intent and auditing.
Fail-closed
The oracle path is fail-closed by design:- There are no
try_wrappers in the critical price path. A trap inside a provider reverts the whole transaction rather than being swallowed. - A missing primary price hard-panics — the protocol never substitutes zero or a default.
- Risk-increasing and liquidation flows reject staleness, out-of-band deviation, and any degraded dual source. A risk-increasing operation never proceeds on a single degraded source.
- A TWAP-to-spot fallback is allowed only under permissive policies, and it emits
OracleTwapDegradedEventso the degradation is observable.
Operator controls
Oracle configuration is owner-gated on the controller and reached through governance in production:| Path | Effect |
|---|---|
Governance propose(AdminOperation::ConfigureMarketOracle) → set_market_oracle_config(asset, config) | Sets strategy, primary, anchor, staleness, tolerances, and sanity bounds; creates or updates AssetOracle(asset). Emits UpdateAssetOracleEvent. |
Governance propose_edit_oracle_tolerance → set_oracle_tolerance(asset, tolerance) | Updates the four-band OraclePriceFluctuation. |
ORACLE role entrypoint:
| Endpoint | Effect |
|---|---|
disable_token_oracle(caller, asset) | Moves a market from Active to Disabled, blocking oracle-dependent risk-increasing operations. Emits OracleDisabledEvent. |
Next
Markets
The market lifecycle that oracle configuration activates.
Risk parameters
Per-market tolerance bands, staleness windows, and sanity bounds.
Security model
The fail-closed posture in the wider trust model.

