Crypto oracle staleness measures the age of an observed report; deviation describes a price change that can trigger a new update. A heartbeat supplies a separate time-based update trigger. These settings do not make every returned price suitable for a decision. Check the exact feed, its reported timestamp and your application's maximum-age policy, then evaluate price disagreement and chain health separately.
Two update triggers do not define your acceptance rule
A push-based price feed need not publish a new on-chain value for every underlying market movement. Chainlink documents two update triggers for its Data Feeds: a deviation threshold for changes in the monitored data and a heartbeat threshold for elapsed time when changes remain within the deviation parameters. The actual settings belong to a particular feed and network. Chainlink's explanation of heartbeat and deviation triggers.
Keep three quantities separate:
| Quantity | Whose rule is it? | What it answers |
|---|---|---|
| Deviation threshold | Feed configuration | When should a price change trigger an update? |
| Heartbeat | Feed configuration | When should elapsed time trigger an update? |
| Maximum accepted age | Consumer policy | Is the report actually observed recent enough for this decision? |
A consumer may require a report younger than the heartbeat. That can deliberately make the application unavailable during a quiet market: the feed may be operating under its configuration while the consumer rejects its latest report. The mismatch needs an explicit design decision, not a silent increase in the age limit.
Conversely, a deviation threshold is not a promise that the published value always remains within that percentage of every exchange price. Triggering, reporting and inclusion on a chain are separate events. Chainlink warns that congestion and network downtime can affect update frequency. Chainlink's network and data-source risk considerations.
Read the report's timestamp, not the RPC response time
For Chainlink's EVM interface, latestRoundData() returns the latest round's answer, round identifier and timestamps. Its updatedAt field describes when the round was updated; startedAt describes when the round started. Retrieve the feed's decimal precision rather than assuming that every asset uses the same scale. Chainlink's AggregatorV3Interface reference.
A successful request made now can return an old round. The current block's timestamp and the application's receive time do not replace updatedAt. Nor should that field be described as the timestamp of every underlying exchange trade used to construct the report.
For an on-chain check, define report age relative to the execution block's timestamp. For an off-chain monitor, retain the block identifier, block timestamp and local observation time so a lagging RPC endpoint does not masquerade as current chain state. The general market-data freshness guide covers arrival and observation clocks; here the extra question is which oracle report the chain actually exposes.
Before subtracting timestamps, reject missing or impossible values under the chosen policy. A zero timestamp, an unexpected future timestamp or an invalid asset-price value should produce an unavailable state, not an age that looks acceptable after arithmetic wraps or a parser applies the wrong unit.
A worked table: age and disagreement can fail independently
Consider an invented positive asset/USD feed. Its last observed report is 2,000 USD at 12:00:00 UTC, with a hypothetical 0.5% deviation trigger and 3,600-second heartbeat. These are teaching inputs, not the current settings of a named feed.
The example consumer requires report age of at most 300 seconds and disagreement of at most 0.75% against a separately verified, fresh comparison reference for the same asset and currency. Assume all other checks pass. Define the comparison as:
gap % = |comparison price − oracle price| ÷ oracle price × 100
The oracle price must be positive for this example. A zero denominator makes the percentage undefined and fails validation. Using the comparison price as denominator would define a different metric; record the convention.
Each row below is an independent simulated check, not a chronological history of accepted feed updates.
| Decision time | Observed report time | Comparison price | Report age | Gap | Example result |
|---|---|---|---|---|---|
| 12:04:00 | 12:00:00 | 2,006 USD | 240 s | 0.30% | Pass these two checks |
| 12:08:00 | 12:00:00 | 2,006 USD | 480 s | 0.30% | Reject: age exceeds 300 s |
| 12:02:00 | 12:00:00 | 2,024 USD | 120 s | 1.20% | Reject: disagreement exceeds 0.75% |
| 12:05:00 | 12:00:00 | 2,006 USD | 300 s | 0.30% | Pass at the inclusive age boundary |
| 12:02:00 | 12:03:00 | 2,006 USD | Invalid | Not evaluated | Reject: future report time |
The second row can be within the hypothetical heartbeat while failing the stricter consumer policy. In the third row, the observed report is young but the comparison gap is large. An expected deviation-triggered update is not evidence that a new report has already arrived. Wait for verifiable state or follow the defined rejection policy.
The independent reference does not establish the oracle is wrong. Differences in asset identity, venue coverage, timestamp or USD versus a stablecoin quote can explain disagreement. The contract and symbol identification guide helps establish that both numbers refer to the intended object before comparing them.
A pull oracle has a different update path
Do not apply the push-feed story to every oracle product. Pyth Core's documented EVM pull integration submits an update on-chain before reading it. Its getPriceNoOlderThan method accepts a caller-selected age threshold and can reject stale data. An off-chain update being available does not itself establish that the consumer chain has received it. Pyth's EVM pull-integration sequence.
Pyth Core's price object also includes publishTime, an exponent and a confidence value. Preserve those fields under their documented meanings instead of copying Chainlink field names into a generic record and losing the distinction. Pyth Core's age-bounded price API.
An update transaction broadcast to the network is still pending evidence. Check the result and the report subsequently read; a failed submission cannot refresh the stored price.
A recent report can coexist with a chain problem
On an applicable L2 network, report age alone does not describe whether users had normal access to transactions during a sequencer outage. Chainlink documents checking a supported sequencer uptime feed and applying a recovery grace period. The status timestamp has its own semantics: it indicates when the sequencer feed changed status. It is not the asset price's update timestamp. Chainlink's sequencer checks and recovery example.
Treat that as a separate gate where the network and application require it. A fixed grace period copied from example code is not a universal recovery recommendation. Likewise, “age passed” should not override an unresolved chain-status failure.
Fallback behavior needs equal care. Automatically replacing a stale report with one thin venue's latest trade can change the data definition just when markets are stressed. A documented policy may pause new risk, request review or use a previously evaluated alternative. Log the reason and source actually selected.
Keep a decision receipt that can be reproduced
For each check, retain a compact worksheet with these fields:
- Feed identity: network, proxy address or feed ID, asset pair and price scale.
- Observed evidence: raw answer, report identifier, report timestamp, block identity and observation time.
- Acceptance policy: maximum age, inclusive or exclusive boundary, comparison denominator and policy revision.
- Separate conditions: comparison source and timestamp, chain or sequencer status, and any recovery condition.
- Outcome: each gate's result and the exact reason for rejection or acceptance.
Exercise the boundary with a report exactly at maximum age, one second beyond it, a future timestamp and a failed update transaction. Also check a young report with a large comparison gap. These fixtures test whether the acceptance rule matches its written definition; they do not establish trading safety.
Passing an oracle check does not establish a fill price
A reference price can be acceptable for valuation while the intended swap faces different liquidity. The concentrated-liquidity execution guide explains how active ranges and the swap path affect execution. An oracle-age check does not replace that inspection.
Order units remain separate too. A market buy specified in base units or quote budget needs the correct amount semantics; an oracle reference does not guarantee how many units that budget will acquire. For centralized derivatives, use the venue's documented mark-price and liquidation rules when assessing margin rather than substituting an external oracle.
This AI-assisted article follows the editorial policy. The worksheet describes general oracle integration checks and makes no claim that KLINEO supplies these feeds, DEX execution or the illustrated acceptance controls.
