Skip to content

Research notes

Chainlink Data Feeds vs Data Streams

Follow a stored feed read and two signed reports through one illustrative clock, then check schema differences and self-managed publication responsibilities.

Ivory paper tray beside an envelope passing through a lime-lit ceramic aperture on a black surface.
Illustration for research notes; examples in this article are educational.

Chainlink Data Feeds let a consumer read an answer already stored onchain. Data Streams deliver signed reports offchain that an application can retrieve and verify onchain when needed. The difference changes the integration: feeds require checks on the stored answer, while streams require report retrieval, verification and application acceptance rules. A familiar feed interface can also sit above a self-managed publication system, so identify who writes and monitors it.

Follow the value to the consumer

For a conventional single-value EVM price feed, an aggregator stores updates and a proxy exposes the latest round. A consuming contract reads that state during its transaction. The read does not ask the oracle network to generate a fresh answer. Chainlink describes the proxy, aggregator and consumer roles in its Data Feeds overview.

Data Streams uses another delivery path. A decentralized oracle network agrees on data and signs a report. The Aggregation Network makes those signed reports available through APIs, including REST and WebSocket access. An application retrieves a payload and supplies it for verification when it needs to use the data onchain. See the Data Streams architecture.

That distinction is about delivery and state, rather than a guarantee that one returned number is suitable for a particular action.

Integration questionConventional single-value EVM feedDirect Data Streams consumer
What reaches the consumer?Stored round data through a proxyA retrieved signed report payload
Who supplies the update transaction?The feed's publication systemThe application supplies a report to its verification transaction
What must the application inspect?Identity, scale, answer and round timestampsIdentity, schema, verified fields and report timestamps
Does reading or fetching refresh onchain application state?A read alone does notA fetch alone does not

This comparison concerns single-value EVM price feeds and the standard Streams API path. Other Data Feed families have different interfaces. Also, an old tutorial built around Streams Trade should not be treated as the default integration: the current architecture documentation marks that approach as being deprecated in favor of Chainlink Runtime Environment, or CRE.

The returned objects have different meanings

An EVM feed's latestRoundData() returns a round identifier, an answer, startedAt, updatedAt and answeredInRound. Read decimals() to interpret the answer's scale. The current Data Feeds API reference marks answeredInRound deprecated; it also says the aggregator's historical minAnswer and maxAnswer values are unused on most feeds. Those fields should not be presented as universal modern freshness or circuit-breaker controls.

A Streams payload needs a decoder matched to its actual schema. In the Crypto Advanced v3 schema documented on 28 October 2026, price is a consensus median, alongside bid and ask fields for simulated liquidity impact. The report also contains its identity, timestamps, expiration and legacy fee fields. Receiving that struct does not make its bid and ask a venue's executable quote or a reconstruction of individual orders. The distinction between Level 1, Level 2 and Level 3 market data explains why field names alone cannot establish order-book detail.

The current Crypto Standard v2 schema is used by TWAP streams. Its price represents the stream's configured averaging window, and it has no bid or ask fields. Replacing a v3 median with a v2 TWAP changes the economic meaning even if both decoded values look plausible. Record the stream identity and window along with the schema version.

Scaling remains necessary after decoding. If the application combines two prices, check their units and observation alignment before division; the Chainlink cross-rate scaling walkthrough examines that additional calculation.

Keep the timestamps in their own roles

A report's arrival time tells you when your client received it. It does not replace the times inside the report. For the stored feed, updatedAt is the round's update time, rather than the timestamp of every underlying exchange trade.

For Streams, validFromTimestamp and observationsTimestamp delimit the report's covered time window in seconds. Window widths can vary; Chainlink documents how a following report can absorb an observation gap. Read the endpoints rather than inventing a fixed interval. A TWAP's averaging window is a separate concept from this report window. These distinctions follow Chainlink's report timestamp definitions.

The execution block time answers another question: how old is the observation when the transaction actually uses it? A report fetched promptly can wait in a transaction queue. Your maximum accepted age therefore needs evaluation at use, with missing or future timestamps handled before subtraction.

Finally, expiresAt is the schema's report-expiration field. A long remaining lifetime does not establish that a price meets a much shorter application freshness rule. Write both conditions explicitly instead of treating expiration as the entire acceptance policy.

For historical event pricing, window membership matters too. A recent report can still fail to cover the timestamp the business operation requires. Conversely, a report whose observation ended shortly before execution can satisfy a declared current-use age policy without claiming that its covered window includes the execution block.

A constructed workflow: fetched, verified, accepted

The following is an illustrative workflow for a hypothetical XYZ/USD application. It is not live data, a latency benchmark or a recommended age limit. All times occur on one UTC day. Assume the intended identities, positive price values and scales have been checked, and the stated Streams verifications succeed.

The stored feed's latest answer is 2,000 USD per XYZ, updated at 12:00:00. Report A carries 2,001 USD, covers 12:00:06 through 12:00:07 and expires at 12:01:07. Report B carries 2,002 USD, covers 12:00:08 through 12:00:09 and expires at 12:01:09. These prices are already normalized for the comparison.

Our illustrative application uses the feed's update time and each Streams report's observation time as their respective age anchors. It accepts ages of at most five seconds, including equality, and requires execution before a Streams report's expiration. Its stored “last accepted observation” for Streams may advance but must not move backward. These are application rules, not additional claims about what every Chainlink verifier enforces.

Time UTCOperationAge under policyOutcomeLast accepted observation
12:00:08Read the stored feed8 secondsReject under the five-second ruleNone
12:00:08Fetch and cache A1 second locallyNo onchain acceptance yetNone
12:00:09Verify and evaluate A2 secondsAccept A12:00:07
12:00:10Verify and evaluate B1 secondAccept B12:00:09
12:00:11Receive A again4 secondsReject older observation12:00:09

At 12:00:09, A's age is nine minus seven, or two seconds. Its observation window ended before execution, but the declared age rule allows that gap. If the operation instead needed the price covering an event at 12:00:06, A's window includes that event. In this direct-consumer design, verifying A changes only the state our consumer chooses to write; it does not refresh the separate feed proxy.

The last row shows why authenticity and freshness do not settle every state question. A remains young enough and has not expired, yet accepting it would move this application's stored observation backward after B. Checking ordering prevents that rollback. This rule does not imply that two distinct operations can never use the same report; action identifiers and duplicate-action handling need their own policy.

Now take a separate branch from the fetch at 12:00:08: no report has yet been accepted, and A's transaction arrives only at 12:00:14. A is seven seconds old and still 53 seconds from expiration. It fails the age rule. A successful verification cannot turn seven seconds into five, and fetching A at 12:00:08 did not reserve its acceptability.

The feed's rejection in this fixture also does not establish a feed malfunction. A deliberately strict consumer policy can reject an answer while the publication system follows its configured update triggers. The broader oracle staleness and deviation guide separates those responsibilities.

Verification is a boundary, not the whole decision

For the documented EVM path, the consumer calls VerifierProxy.verify() or verifyBulk() and decodes the verified response using the appropriate report schema. The proxy routes to the underlying verifier. Chainlink explicitly assigns report suitability to the application in its onchain verification reference.

An offchain preview can help reject malformed inputs early, but application decisions should use verified fields. A caller able to submit a genuine report for an unintended asset must not pass merely because the signature is authentic. Bind the expected stream identity, quote unit and schema to the operation before using its price.

Keep network conditions separate as well. Where applicable, a sequencer uptime feed and a price feed answer different questions. A valid price report does not itself establish sequencer recovery or normal user access.

The self-managed feed exception changes ownership

Push and pull are not disjoint product boxes. Chainlink documents self-managed feeds in which a third party uses a CRE workflow to read Data Streams and write values into a feed proxy. Downstream consumers can retain a familiar feed interface while the publication system behind it changes.

For those deployments, the operator is responsible for reading, transforming and writing the correct values on schedule, maintaining the contracts and supporting integrators. Chainlink Labs does not manage their onchain publication monitoring or service levels, and their proxy addresses must be obtained from the operator rather than official feed catalogs. See the self-managed feeds responsibilities.

Consequently, a proxy address and a successful latestRoundData() call are insufficient evidence of the operating model. Ask who can write updates, how a verified report becomes a stored value, what timestamp survives that transformation and who investigates a missed write.

Build one reproducible acceptance record

For each candidate integration, complete this short worksheet:

  1. Identify the network, proxy or stream ID, operator, asset pair, units and schema. For self-managed publication, document the transformation and responsible operator.
  2. Record the evidence actually used: block identity and stored round, or signed report payload and successful verification result. Keep arrival time separate.
  3. Write the timestamp rules: age boundary, expiration, any required event-window membership, observation ordering and network recovery conditions.
  4. Define failure outcomes for missing data, invalid verification, wrong identity, stale reports and delayed transactions. Include duplicate actions separately from repeated reports.
  5. Exercise the boundaries: age five versus six seconds in this fixture, an observation from the future, A after B, and the delayed A branch.

The useful final record says which value was accepted, under which rule, at which execution time, and which application state changed. If it only says “latest price fetched,” the path from available data to an accepted decision is still incomplete.

Keep the context with the example.

Each worked example is illustrative, with its sources and assumptions in the article. Read our editorial standards for authorship, verification and corrections.