> For the complete documentation index, see [llms.txt](https://docs.kpk.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.kpk.io/funds/infrastructure/onchain-accounting/concepts/stale-prices-and-sequencer.md).

# Stale prices & sequencer

How the NAV Calculator signals that a reading is unreliable — stale price feeds, an unhealthy quote asset, or a down L2 sequencer (hard signals that must **block** pricing), plus a soft **divergence** signal that flags a price the primary and monitor feeds disagree on. A reading carrying any hard signal must not be used for subscription or redemption pricing.

**Source:** [`PriceFeedLib.sol`](https://github.com/karpatkey/onchain-accounting/blob/main/src/prices/PriceFeedLib.sol)

***

## Stale prices and sequencer status

NAV v2 does not collapse staleness into a single flag. Instead:

* `NAV.stalePriceAssets` — assets whose **primary** feed was stale at read time. An empty array means every priced asset had a fresh primary. A registered asset that has a live position but **no usable price feed** reaching the NAV summation is treated the same way: it is priced as 0, marked stale, surfaced here, and summed as a zero-value leg — rather than reverting the entire NAV/positions read. (The explicit query paths — `getPriceData` / `calculateValue` — still revert `PriceFeedNotSet` for a feedless asset, so misconfiguration stays visible to tooling.)
* `NAV.sequencerDown` — `true` if the L2 sequencer uptime feed is stale or within its grace period (L2 chains only; always `false` on Mainnet).
* `NAV.quoteAssetStale` — `true` if a non-USD quote asset's own feed was stale; `value` then falls back to USD even though `quoteAsset` is non-zero. Callers must check this flag before treating `value` as denominated in the requested currency. The same signal is surfaced beyond the `NAV` struct: each `Position` carries a `quoteAssetStale` bool, and `calculateValue` returns it alongside `value` — so a stale-quote USD fallback is flagged on those paths too, not only on the NAV reading.

{% hint style="warning" %}
If `stalePriceAssets.length > 0`, `sequencerDown == true`, or `quoteAssetStale == true`, the NAV reading should **not** be used for subscription or redemption pricing. Use `healthCheck()` to poll chain health without computing a full NAV.
{% endhint %}

***

## Price divergence (the `irregular` signal)

Alongside the hard signals above, the NAV Calculator emits a **soft** divergence signal per asset. Each asset is priced by a single [primary feed](/funds/infrastructure/onchain-accounting/price-feeds.md#primary-feed-and-divergence-monitors); any additional trustworthy source is registered as a **monitor** feed that is read only to check the primary, never to price NAV.

* `NAV.irregularPriceAssets` — assets whose primary price diverged from the worst-case (furthest) healthy monitor beyond the asset's `divergenceToleranceBps`, **or** (USD-pegged stablecoins) strayed from `$1` beyond its `pegToleranceBps`. Each `Position` carries an `irregular` bool, `getPriceData` exposes `irregular` / `divergenceBps` / `monitorFeedCount`, and `getPriceDivergence` returns the full read (primary, monitor median, worst-case bps, direction).
* The check is **suppressed while the primary is stale** — a stale primary is already reported via `stalePriceAssets`.

{% hint style="info" %}
`irregular` is a **quality signal, not a validity flag**: `value` is still populated from the primary feed and the NAV Calculator **never reverts** on divergence. Unlike the hard signals, a non-empty `irregularPriceAssets` does not by itself invalidate the reading — it tells the offchain subscription/redemption processor to investigate, widen its band, or alert before the divergence grows into a stale/blocking condition. See [Price feeds](/funds/infrastructure/onchain-accounting/price-feeds.md#the-divergence-and-irregular-signal) for the tolerances in use.
{% endhint %}

***

## When no monitor answered (`monitorsUnhealthyPriceAssets`)

A clean `irregular` flag can mean two very different things: the primary was **checked and agreed with its monitors**, or it was **checked by nothing that answered**. The divergence computation is guarded on there being at least one healthy monitor, so when every monitor for an asset is stale, reverting, or reporting a non-positive price, the arithmetic never runs — `divergenceBps` is `0` and `irregular` is `false` *for want of data, not for want of divergence*. From outside, the two cases were the same bytes.

* `NAV.monitorsUnhealthyPriceAssets` — assets that **have monitor feeds configured but where none was readable** at query time. For these, absence from `irregularPriceAssets` carries no information.

Three boundaries matter, because each one is a plausible misreading:

* **It is not the "no monitors configured" case.** That is static configuration, not a runtime failure, and it is deliberately excluded — an asset with nothing configured never appears here however long it goes unchecked. Derive that case off-chain from **`getMonitorFeedCount(asset) == 0` alone**. Do *not* additionally require `divergenceToleranceBps == 0`: that conjunction is unsatisfiable for any asset that ever had a monitor, because setting a tolerance to `0` is rejected while removing a monitor leaves the tolerance untouched. An AND-recipe would silently never flag an asset whose monitors were all removed.
* **Assets whose own primary is unusable are excluded** — stale feed, sequencer down, or a primary whose 8-decimal normalisation degenerates to zero. In each of those the check was suppressed by the *primary*, `stalePriceAssets` already reports the staleness cases, and naming the monitors would point an operator at the wrong contract.
* **It flags the loss of the divergence check, not of all checks.** The `$1` peg guard is monitor-independent, so a stablecoin with a non-zero `pegToleranceBps` can appear here and still be peg-checked.

{% hint style="warning" %}
**Scope: assets the account holds positions in**, exactly like `stalePriceAssets` and `irregularPriceAssets`. The array is built from the assets the account's positions touched, not from the whole registry, so an empty array means "monitors healthy **or** no position held". Reconciling it against the registry-wide per-asset view (`getPriceDivergence(asset).monitorFeedCount`) will show differences that are this scoping rather than a defect.

Two related surfaces deliberately do **not** carry the signal, because `NAVCalculator` is close enough to the EIP-170 contract-size limit that widening them was not affordable: `healthCheck()` — which *does* iterate the whole registry — and `Position.irregular` on `getAccountPositions`, which retains the original ambiguity with no `NAV` struct alongside to join against. Use `getAccountNavVerbose`, whose `VerboseNAV` embeds the `NAV`, when you need positions and the third array together.
{% endhint %}

This is not a rare path. Most monitored assets carry **exactly one** monitor, and many of those have no peg backstop either — so for them "every monitor dead" means *one feed drifting past its heartbeat*, an ordinary event rather than a correlated outage. It is also the shape a provider sunsetting a feed produces: no primary on any chain uses a monitor-only provider, so a sunset degrades the cross-check to "checked by nothing that answered" — visible here — rather than moving the NAV.

***

## L2 sequencer uptime check

On Layer 2 chains (Arbitrum, Base, Optimism, Gnosis), the NAV Calculator additionally checks the **Chainlink L2 Sequencer Uptime Feed**. If the sequencer is down or within its grace period after recovery, `NAV.sequencerDown` is `true` and the entire chain's NAV reading should not be used for pricing.

```solidity
function checkL2SequencerUptime() external view returns (bool stale);
```

Example (illustrative):

```
checkL2SequencerUptime()   → false    // Arbitrum/Base/etc.: sequencer up
checkL2SequencerUptime()   → true     // sequencer down or still in grace period
checkL2SequencerUptime()   → false    // Ethereum mainnet: no uptime feed, always false

healthCheck()
→ (false, [], [])          // healthy: sequencer up, no stale assets, no irregular assets
→ (true,  [], [])          // L2 sequencer down or in grace period
→ (false, [], [0xae78…86CA]) // sequencer up, but an asset's primary diverged from its monitors
```

{% hint style="info" %}
`NAV.sequencerDown` is always `false` on Ethereum Mainnet — there is no sequencer feed to check. The uptime feed and grace period are configured by the admin (`setChainlinkL2SequencerUptimeFeed`, `setChainlinkL2SequencerGracePeriod`).
{% endhint %}

***

## Related

* [NAV Calculator](/funds/infrastructure/onchain-accounting/contracts/nav-calculator.md) — the `NAV` struct fields and `healthCheck()`.
* [Price feeds](/funds/infrastructure/onchain-accounting/price-feeds.md) — primary feed, divergence monitors, and per-feed staleness rules.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.kpk.io/funds/infrastructure/onchain-accounting/concepts/stale-prices-and-sequencer.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
