agents/openai.yaml
interface:
display_name: "Chainlink Data Feeds Skill"
short_description: "Help with Chainlink Data Feeds tasks"
icon_small: "./assets/chainlink-icon.png"
icon_large: "./assets/chainlink-icon.png"
brand_color: "#0847F7" # Chainlink Blue
assets/data-feeds-docs-index.md
# Data Feeds Documentation Delta Cache
## Contents
- Contract and aggregator inspection
- ENS feed discovery
- Feed selection and market-data quality
- Registry and deprecation deltas
- MVR proxy administration and client deltas
- SmartData, Proof of Reserve, and self-managed deltas
- Solana deltas
- Aptos deltas
- Starknet deltas
- SVR searcher protocol deltas
- Tokenized equity deltas
- Tron deltas
- Source retention ledger
Topic-indexed facts absent from the curated references, which remain the workflow and safety owners. Treat addresses, versions, endpoints, network lists, and parameters as cached; verify the linked official source.
## Contract and aggregator inspection
Sources: [Data Feeds overview](https://docs.chain.link/data-feeds.md), [index](https://docs.chain.link/data-feeds/index.md), [API reference](https://docs.chain.link/data-feeds/api-reference.md), [full-text fallback](https://docs.chain.link/data-feeds/llms-full.txt).
- A proxy can expose the current underlying aggregator address. Block explorers and verified ABIs can then show aggregator owner, configuration, transmissions, and implementation-specific methods. Do not turn inspection into a consumer dependency: application reads still go through the proxy so aggregator upgrades do not break the application.
- Aggregator implementations vary by feed and network. Call `typeAndVersion()` and inspect the verified source/configuration before reasoning about internals; do not assume every aggregator is `AccessControlledOffchainAggregator`.
- `AccessControlledOffchainAggregator` metadata may include `LINK`, `owner`, `billingAccessController`, `requesterAccessController`, `checkEnabled`, `minAnswer`, and `maxAnswer`. The min/max answer fields are largely unused on many feeds, so applications need their own appropriate circuit breakers rather than relying on those fields.
- Additional inspection methods can include `getBilling()`, `getConfig()`, `latestConfigDetails()`, `latestTransmissionDetails()`, `transmitters()`, and `hasAccess(address,bytes)`. These are aggregator/configuration tools, not substitutes for `AggregatorV3Interface` consumer reads.
- Legacy aggregator getters such as `getAnswer`, `getTimestamp`, `latestAnswer`, `latestRound`, and `latestTimestamp` are deprecated. Prefer the proxy's V3 round methods.
- `@chainlink/contracts` is the EVM interface package. The exact source paths and samples live in [official-sources.md](../references/official-sources.md); fetch a single file rather than browsing an entire repository.
## ENS feed discovery
Source: [ENS integration](https://docs.chain.link/data-feeds/ens.md).
- Feed names use `<base>-<quote>.data.eth`, for example `eth-usd.data.eth` and `btc-usd.data.eth`.
- Subdomains distinguish roles: `proxy.<name>` resolves the stable consumer proxy, `aggregator.<name>` the current underlying aggregator, and `proposed.<name>` a proposed aggregator. Consumers should normally resolve/use the proxy.
- Cached Ethereum Mainnet Chainlink resolver for `data.eth`: `0x122eb74f9d0F1a5ed587F43D120C1c2BbDb9360B`. Verify it before use.
- Resolver `AddrChanged` events announce address changes. Filter the indexed node with the ENS namehash of the full subdomain, such as `aggregator.eth-usd.data.eth`.
- Reverse lookup is not supported. JavaScript can use `web3.eth.ens.getAddress("eth-usd.data.eth")`; Solidity callers resolve a `bytes32` node through the ENS registry/resolver. Any copied Solidity example is unaudited and requires review.
## Feed selection and market-data quality
Sources: [Price Feeds](https://docs.chain.link/data-feeds/price-feeds.md), [selecting feeds](https://docs.chain.link/data-feeds/selecting-data-feeds.md), [developer responsibilities](https://docs.chain.link/data-feeds/developer-responsibilities.md), [data sources](https://docs.chain.link/data-feeds/data-sources.md).
- Official selection categories are **Low**, **Medium**, **High**, and **Very High Market Pricing Risk**, plus **New Token**, **Custom**, and **Deprecating**. A category informs diligence; it does not replace application-specific risk parameters.
- Medium-risk drivers include low or inconsistent liquidity, large venue spreads, concentration on one exchange, cross-rate exposure, migrations or market events, provider dispersion, and changing source availability.
- High-risk feeds have heightened medium-risk factors and can be candidates for deprecation. Very-high-risk conditions include extreme volatility, hacks, bridge failures, delistings, severe liquidity loss, and other exceptional events.
- New Token feeds have limited history. Custom feeds need extra scrutiny and can represent an onchain single source, Proof of Reserve, an exchange rate rather than a market price, technical metrics, TVL, or a custom index.
- Price Feeds normally aggregate multiple sources through independent node operators, but exceptions include single-source feeds and calculated values. Confirm the sourcing model for the exact feed rather than assuming decentralization from the product name.
- Most crypto price, crypto state price, forex, precious-metals, US-oil, and US-equity feeds use at least three vendors/aggregators. UK and Euro ETF data can use two or more vendors and be delayed by 15 minutes. Data Link long-tail crypto can be a single aggregator source.
- Market integrity risks include spoofing, ramping, bear raids, cross-market manipulation, wash trading, and frontrunning. Low-liquidity assets are more vulnerable. The integrator owns monitoring, code/dependency audit, end-user disclosure, circuit breakers, and contingency behavior.
- Watch the Chainlink changelog and the Discord notification channel for classification and deprecation changes. Current classifications and sources belong to the live selection page.
## Registry and deprecation deltas
Sources: [contract registry](https://docs.chain.link/data-feeds/contract-registry.md), [deprecating feeds](https://docs.chain.link/data-feeds/deprecating-feeds.md), [price address directory](https://docs.chain.link/data-feeds/price-feeds/addresses.md).
- `IFlags.getFlag(proxy) == true` means the proxy is currently listed as official and active for that network. Inactive feeds are removed; the registry is maintained as deployments change.
- Each network has a different Flags contract. Never copy an address from another network or infer it from a proxy. Fetch the live table before constructing a registry call.
- Deprecation candidates include feeds with little/no usage or no viable economic-sustainability path. Data-quality monitoring stops two weeks before the published deprecation date.
- The live table fields include network, pair, deprecation date, deviation, heartbeat seconds, decimals, aggregator address, asset name/type, and market hours. Use it before mainnet launch and during ongoing monitoring.
- Officially documented feeds are reviewed; community/custom deployments can have additional risks. Registry status and documentation listing do not remove the integrator's diligence obligations.
## MVR proxy administration and client deltas
Sources: [MVR API](https://docs.chain.link/data-feeds/mvr-feeds/api-reference.md), [guide index](https://docs.chain.link/data-feeds/mvr-feeds/guides.md), [Solidity](https://docs.chain.link/data-feeds/mvr-feeds/guides/evm-solidity.md), [ethers v5](https://docs.chain.link/data-feeds/mvr-feeds/guides/ethersjs.md), [viem](https://docs.chain.link/data-feeds/mvr-feeds/guides/viem.md).
- `IBundleAggregatorProxy` combines bundle reads with common aggregator metadata and keeps consumers compatible across aggregator changes when they remain on the proxy.
- Administrative/view functions can include `aggregator()`, `proposedAggregator()`, and `confirmAggregator(address)`. They describe proxy upgrade state; ordinary consumers use the bundle read trio documented in `mvr-feeds.md`.
- The guide hub separates onchain Solidity, offchain ethers v5, and offchain viem paths. Ethers v6 differs from the cached v5 guide. Prefer the caller's installed client and the schema-neutral sequence in the curated reference.
- Ethers v5 uses `ethers.utils.defaultAbiCoder.decode` and `ethers.utils.formatUnits`; viem uses `decodeAbiParameters`, `readContract`, and `formatUnits`. Both need `RPC_URL`, a proxy address, the exact documented schema, per-field decimals, and a heartbeat-derived staleness threshold.
- A decimals array should align with the schema length. Numeric values can be kept raw to avoid Solidity truncation; booleans and other nonnumeric fields are not scaled.
- Examples in the official guides are unaudited and not production-ready without feed-specific validation, access/risk design, and review.
## SmartData, Proof of Reserve, and self-managed deltas
Sources: [SmartData](https://docs.chain.link/data-feeds/smartdata.md), [SmartData addresses](https://docs.chain.link/data-feeds/smartdata/addresses.md), [self-managed feeds](https://docs.chain.link/data-feeds/self-managed-feeds.md).
- Proof of Reserve answers can be quantities such as ounces or token counts, not prices. Interpret the feed description, units, decimals, and source model before using the answer in collateral logic.
- Offchain reserve data can come from custodians, fund administrators, auditors, asset managers, regulated appraisers, or other approved sources; cross-chain PoR can read onchain reserves from the asset-holding chain.
- Wallet-address-manager configuration differs across feeds and chains. A self-reporting manager can include addresses whose ownership is not cryptographically verified; an issuer could inflate reported reserves with addresses it does not control. Inspect each feed's address-page disclosure.
- Chainlink Labs disclaims responsibility for the accuracy of self-reported reserves. Treat issuer reporting and address ownership as trust assumptions and perform independent data-quality/issuer-risk analysis.
- A self-managed feed's third-party publication path has no Chainlink Labs monitoring for heartbeat or deviation compliance, end-to-end latency, write correctness, or publication SLA. Addresses come from the chain/operator rather than the public catalog.
- The self-managed architecture uses a CRE workflow to read Chainlink Data Streams and write a standard Data Feeds proxy. A familiar consumer interface does not imply Chainlink-operated publication.
## Solana deltas
Sources: [Solana overview](https://docs.chain.link/data-feeds/solana.md), [onchain v2 guide](https://docs.chain.link/data-feeds/solana/using-data-feeds-solana.md), [offchain guide](https://docs.chain.link/data-feeds/solana/using-data-feeds-off-chain.md).
- Programs contain logic and accounts contain feed state. Programs are stateless; local program tests are possible, but live Chainlink feeds require a supported onchain cluster. Price Feeds are on Mainnet and Devnet, not Solana Testnet.
- Cached OCR2 feed owner/program ID for v2 direct account validation: `HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny`. Cached offchain Data Feeds Store Program ID: `cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ`. Verify both before use.
- `chainlink_solana::v2::read_feed_v2(feed_data_bytes, feed_owner_pubkey_bytes)` returns a parsed feed exposing `latest_round_data()`, `description()`, and `decimals()`. V2 direct account reads replace the deprecated v1 CPI pattern and reduce compute use.
- Cached dependencies are `chainlink_solana = "2.0.8"` and, for Anchor, `anchor-lang = "0.31.1"`. Package versions can move.
- Offchain clients use `OCR2Feed.load(programId, provider)` and `onRound(feedAddress, listener)`. Cached packages are `@chainlink/solana-sdk` and `@project-serum/anchor`; the old guide states Node.js 14 or later.
- Anchor requires `ANCHOR_PROVIDER_URL` and `ANCHOR_WALLET` even for read-only calls. A wallet file needs no lamports for reads, but it is still a credential: do not expose, solicit, or commit wallet/private-key material.
- Network congestion can reduce update frequency. Monitor Solana status and validate feed freshness rather than treating expected cadence as guaranteed.
## Aptos deltas
Source: [Aptos guide](https://docs.chain.link/data-feeds/aptos.md).
- `data_feeds::router::get_benchmarks(account, feed_ids, billing_data)` accepts a vector of feed IDs and returns `vector<Benchmark>`; extract values with `get_benchmark_value` and timestamps with `get_benchmark_timestamp`.
- The reference's BTC/USD testnet feed ID is cached. The Chainlink platform and data-feeds package addresses are also network-specific; retrieve their complete live values from the guide rather than using shortened examples.
- A Move module can store `PriceData` under the signer and expose a `#[view] get_price_data(account_address)` accessor. This persistence pattern is optional; it is not required for a one-shot router read.
- Publishing or invoking an entry function consumes gas in Octas (`1 APT = 100,000,000 Octas`). Read model and feed identity remain router-plus-feed-ID, not one contract address per pair.
## Starknet deltas
Sources: [Starknet overview](https://docs.chain.link/data-feeds/starknet.md), [Foundry hub](https://docs.chain.link/data-feeds/starknet/tutorials/snfoundry.md), [offchain read](https://docs.chain.link/data-feeds/starknet/tutorials/snfoundry/read-data.md), [consumer](https://docs.chain.link/data-feeds/starknet/tutorials/snfoundry/consumer-contract.md), [Devnet RS](https://docs.chain.link/data-feeds/starknet/tutorials/snfoundry/sn-devnet-rs.md).
- `latest_round_data` returns hex-encoded fields in this order: `round_id`, `answer`, `block_num`, `started_at`, `updated_at`. Timestamps are Unix seconds. Decode by position; do not apply the EVM five-tuple ordering.
- Cached Starknet Sepolia ETH/USD proxy: `0x228128e84cdfc51003505dd5733729e57f7d1f7e54da679474e73db4ecaad44`. RPC provider documentation: [Alchemy supported chains](https://www.alchemy.com/docs/reference/node-supported-chains).
- `starkli call` and `sncast call` can read without an account. A deployed Cairo consumer requires a funded account. The tutorial hub separates read-only, deployed-consumer, and Docker-based Devnet RS flows.
- Cached tutorial versions are Starknet Foundry `0.21.0` and Scarb `2.6.4`; verify current compatibility before following repository scripts.
- ETH/USD cached examples use 8 decimals, but consumers still obtain/confirm precision for the selected feed rather than generalizing that value.
## SVR searcher protocol deltas
Sources: [SVR](https://docs.chain.link/data-feeds/svr-feeds.md), [Ethereum searchers](https://docs.chain.link/data-feeds/svr-feeds/searcher-onboarding-ethereum.md), [Atlas searchers](https://docs.chain.link/data-feeds/svr-feeds/searcher-onboarding-atlas.md).
- Ethereum events can be single-transaction or bundle events; bundle transactions are nonce-ordered. Updates originate from multiple forwarders, one per Node Operator Proxy, so a searcher cannot pin one sender address.
- The nested report decode is `(uint32 observationsTimestamp, bytes32 observers, int192[] observations, int192 juelsPerFeeCoin)`; searchers derive the median observation after decoding `forward` and `transmitSecondary` calldata.
- Atlas represents the oracle update and liquidation as EIP-712 user/solver operations bundled atomically without depending on block builders. The WebSocket JSON-RPC subscription uses method `solver_subscribe` with `userOperations`.
- The solver must simulate locally, sign the exact oracle-provided `gasPrice`, stay within `DappControl.getSolverGasLimit()`, maintain adequate bond for gas reimbursement, use buffers above 10 KB, and reconnect automatically.
- Atlas documentation covers Aave, Compound, and Venus integrations. Listen to the current aggregator, not proxy, for price reports; resolve it from `proxy.aggregator()` and verify live contract/feed addresses.
- Searcher examples and JSON schemas are indexed by exact path in `official-sources.md`. Operational endpoints, contracts, supported protocols, bonds, and auction mechanics can change; fetch the onboarding page before running a solver.
## Tokenized equity deltas
Sources: [tokenized equity](https://docs.chain.link/data-feeds/tokenized-equity-feeds.md), [Ondo](https://docs.chain.link/data-feeds/tokenized-equity-feeds/ondo.md), [providers](https://docs.chain.link/data-feeds/tokenized-equity-feeds/providers.md).
- A tokenized-equity feed prices the issuer's token instrument, not necessarily the raw underlying equity. Issuer methodology can incorporate total-return multipliers, dividends, and corporate actions.
- Provider coverage and quality are strongest during the regular US session and thinner pre-market, post-market, and overnight. Session-aware smoothing can reduce brief illiquid spikes but can lag legitimate rapid moves. Halts are not explicitly flagged.
- Ondo Total Return Value is `Underlying Equity Market Price × sValue`; `sValue` comes from `SyntheticSharesOracle`. Changes at or below 1% per 24 hours apply automatically; larger changes require a scheduled pause and manual confirmation.
- A corporate-action pause is scheduled at least 24 hours in advance; at pause time the feed freezes at its last known good token price until alignment is confirmed and the feed is unpaused. The configured minimum pause duration is at least 10 minutes.
- The provider directory records provider, feed type, calculation, corporate-action handling, documentation, and issuer contact. Provider behavior is not interchangeable. Contact `datafeeds@chain.link` and set protocol risk parameters before integration.
## Tron deltas
Source: [Tron guide](https://docs.chain.link/data-feeds/tron.md).
- Tron uses Solidity-compatible `AggregatorV3Interface`, but account/contract addresses are base58. Nile examples in the curated reference are not EVM hex addresses.
- The official `DataFeedReader.sol` shape exposes latest round data, latest price, decimals, and description. Its offchain TronWeb reader formats `BigInt` answers with runtime decimals and prints round/timestamp metadata.
- Deployment examples use TronBox and a Nile-only account. Never request, expose, paste into chat, or commit `PRIVATE_KEY_NILE`; keep secrets in the user's local environment and never use a production key for a tutorial.
- Cached tooling in the source expects Node.js 20+ and TronBox 3.3+ (the captured example reported TronBox 4.2.0 / Solidity 0.8.23). Tool versions are setup details, not part of the read model, and must be rechecked if compilation fails.
## Source retention ledger
Every source from the former per-URL dump was reviewed. Sources with no remaining delta are represented by their curated fact owner rather than repeated payloads:
| Sources | Curated owner / retained delta |
|---|---|
| `data-feeds.md`, `index.md`, `api-reference.md`, `llms-full.txt` | Contract inspection above; basic proxy/interface facts in `reading-price-feeds.md` |
| `getting-started.md`, `using-data-feeds.md` | Runnable consumer/readers in `reading-price-feeds.md` and starter kit |
| `historical-data.md` | Historical encoding and caveats in `reading-price-feeds.md` |
| `l2-sequencer-feeds.md` | Canonical consumer in `reading-price-feeds.md`; mechanics in `feed-operations.md` |
| `contract-registry.md`, `deprecating-feeds.md` | `feed-operations.md`; operational deltas above |
| `data-sources.md`, `developer-responsibilities.md`, `self-managed-feeds.md` | `feed-operations.md`; selection/trust deltas above |
| `feed-types.md`, `rates-feeds.md`, `smartdata.md` | `feed-types.md`; SmartData deltas above |
| `price-feeds.md`, `price-feeds/addresses.md`, `selecting-data-feeds.md` | `reading-price-feeds.md`, `official-sources.md`, and selection deltas above |
| `mvr-feeds.md`, its API, hub, Solidity, ethers, and viem guides | `mvr-feeds.md`; proxy/client deltas above |
| `svr-feeds.md` and both searcher guides | `svr-feeds.md`; protocol deltas above |
| `solana.md` and both Solana guides | `multi-chain.md`; Solana deltas above |
| `aptos.md` | `multi-chain.md`; Aptos deltas above |
| `starknet.md` and four Foundry/tutorial pages | `multi-chain.md`; Starknet deltas above |
| `tron.md` | `multi-chain.md`; Tron client/security deltas above |
| Tokenized equity overview, Ondo, and providers | `feed-types.md`; issuer-operation deltas above |
| Price, SmartData, rate/volatility, and U.S. macro address pages | `official-sources.md`; no stale address table duplicated here |
| `ens.md` | ENS delta above |
references/feed-operations.md
# Feed Operations
## Contents
- Trigger Conditions
- L2 Sequencer Uptime Feed
- Feed Deprecation
- Contract Registry
- Developer Responsibilities
- Data Sources
- Self-Managed Feeds
- Freshness Rules
Use this file for operational concerns around Chainlink Data Feeds: L2 sequencer checks, feed deprecation, contract registry verification, developer responsibilities, data sourcing, and self-managed feeds.
## Trigger Conditions
Use this workflow for requests like:
- "My consumer is on Arbitrum, do I need to add anything for L2?"
- "How do I check if a feed is officially managed by Chainlink?"
- "Is this feed being deprecated?"
- "What are my responsibilities as a data feed integrator?"
- "Where does the price data actually come from?"
- "What is a self-managed feed?"
Do not use this workflow for basic price feed reading, feed address lookup, or Data Streams / VRF integrations.
## L2 Sequencer Uptime Feed
On L2 rollups, sequencer downtime breaks normal read/write access and can leave stale data in the pipeline. Any contract that reads a price feed on a supported L2 must check the sequencer uptime feed before trusting the price.
### Canonical consumer
Use [`DataConsumerWithSequencerCheck`](reading-price-feeds.md#l2-sequencer-uptime-check), which checks `answer != 0`, enforces the 3600-second recovery grace period, then validates price freshness and bounds.
### Critical implementation details
1. The sequencer uptime feed uses `AggregatorV2V3Interface`, NOT `AggregatorV3Interface`. Using the wrong interface will compile but may produce incorrect behavior.
2. `answer == 0` means the sequencer is up. `answer == 1` means the sequencer is down. This is inverted from what many developers expect.
3. `startedAt` is the timestamp when the status last changed, not the timestamp of the current round.
4. The grace period prevents trusting price data immediately after sequencer recovery. Stale data may still be in the pipeline while the sequencer catches up. One hour (3600 seconds) is the standard default.
5. Always combine the sequencer check with staleness and sanity checks on the price feed itself (`updatedAt` freshness, `price > 0`).
### Update mechanics
- Arbitrum: OCR round approximately every 30s. Nodes call `ArbitrumValidator.validate` via `ValidatorProxy` -> Arbitrum inbox -> L2 `ArbitrumSequencerUptimeFeed.updateStatus` sets status and records L1 timestamp.
- Other networks (Base, OP, Scroll, etc.): L1 `AggregatorProxy` -> `OptimismValidator.validate` -> `L1CrossDomainMessenger.sendMessage` -> `CanonicalTransactionChain.enqueue` -> sequencer -> `L2CrossDomainMessenger` -> `OptimismSequencerUptimeFeed.updateStatus`. Messages are processed in arrival order after sequencer return, so the "sequencer down" flip executes before later dependent transactions.
### Supported networks
Proxy addresses available for: Arbitrum, Base, Celo, Mantle, MegaETH, Metis, OP, Scroll, Soneium, X Layer, zkSync. Retrieve the correct address from `https://docs.chain.link/data-feeds/l2-sequencer-feeds.md`.
### Decision path
If the consumer is on a supported L2: add the sequencer uptime check. If on Ethereum L1 or a network without a sequencer: omit the check but retain staleness and sanity validation.
## Feed Deprecation
Chainlink may deprecate feeds due to low usage or lack of economic sustainability under Chainlink Economics 2.0.
1. Data quality monitoring stops 2 weeks before the deprecation date. After that point the feed may return stale or unreliable data.
2. Deprecation schedule: `https://docs.chain.link/data-feeds/deprecating-feeds.md`
3. Subscribe to the Chainlink Discord `data-feeds-user-notifications` channel for advance alerts.
4. The deprecation table includes: Network, Pair, Deprecation date, Deviation threshold, Heartbeat (seconds), Decimals, Aggregator address, Asset name/type, Market hours.
5. Feeds listed in official Chainlink documentation are reviewed. Community or custom deployments may carry additional risks and require independent due diligence.
Action: before integrating any feed, check the deprecation schedule. Before any mainnet launch, confirm the feed is not scheduled for shutdown.
## Contract Registry
The Flags contract is an onchain registry of official, active Chainlink feeds. Use it to verify that a proxy address is Chainlink-owned and currently active.
### Interface and usage pattern
```solidity
interface IFlags {
function getFlag(address) external view returns (bool);
}
contract FeedVerifier {
IFlags internal flags;
constructor(address _flagsContract) {
flags = IFlags(_flagsContract);
}
function isOfficialChainlinkFeed(address proxy) public view returns (bool) {
return flags.getFlag(proxy);
}
}
```
### Behavior
- `getFlag(proxy)` returns `true` if the feed is official and active. `false` means the feed is not Chainlink-managed or has been deactivated.
- Inactive feeds are removed from the registry.
- Each network has its own Flags contract address. Retrieve the correct address from `https://docs.chain.link/data-feeds/contract-registry.md`.
## Developer Responsibilities
Developers are solely responsible for monitoring and mitigating risks when integrating Chainlink Data Feeds.
Market manipulation can affect feed behavior. Known attack vectors: spoofing, ramping, bear raids, cross-market manipulation, wash trading, frontrunning. Low-liquidity assets are more vulnerable. Application code must be audited before production, and all dependencies vetted. Inform end users of applicable risks.
Required mitigations in application code:
1. Data quality checks: validate staleness (`updatedAt`), sanity (`price > 0`), and sequencer status on L2.
2. Circuit breakers: pause operations when data falls outside expected bounds.
3. Contingency logic: define fallback behavior for feed failure or extreme deviation.
## Data Sources
| Feed Type | Sources | Minimum Count |
|---|---|---|
| Crypto Price | CEX and/or DEX data via aggregators/vendors | 3+ |
| Crypto State Price | DEX pool state data via vendors | 3+ |
| Forex / Precious Metals / US Oil / US Equities | Market data vendors | 3+ |
| UK and Euro ETFs | Market data vendors (15-minute delayed) | 2+ |
| SmartData PoR (offchain) | Direct from custodian / fund administrator / auditor | 1 |
| SmartData PoR (cross-chain) | On-chain data | 1 |
| SmartData NAV (offchain) | Direct from Fund Administrator or Asset Manager | 1 |
| Composite Index | DON-calculated from aggregators/vendors | 3+ |
| Data Link Long Tail Crypto | Single crypto price data aggregator | 1 |
Long-tail crypto feeds using a single source carry higher risk. Evaluate data quality, liquidity distribution, and single-source provider risks before integrating.
## Self-Managed Feeds
Self-managed feeds are written onchain by a chain or third-party operator, not Chainlink node operators. A CRE workflow reads from Chainlink Data Streams and writes updates to a proxy contract. Consumers read via the standard Data Feeds proxy interface.
Chainlink Labs does not manage monitoring, heartbeat compliance, deviation compliance, latency, correctness of onchain writes, or SLAs for these feeds. Proxy addresses are NOT published in Chainlink public feed catalogs -- obtain addresses directly from the chain or operator. Contact: datafeeds@chain.link
Treat self-managed feeds as untrusted until independently verified. Apply the same staleness, sanity, and circuit-breaker checks described in Developer Responsibilities.
## Freshness Rules
1. Retrieve sequencer uptime feed proxy addresses and Flags contract addresses from official Chainlink docs before generating code.
2. Check the deprecation schedule before recommending any specific feed.
3. Do not hardcode proxy addresses, Flags contract addresses, or feed parameters. Always point the user to the official source for current values.
4. Verify network support before recommending L2 sequencer checks.
references/feed-types.md
# Feed Types
## Contents
- Trigger Conditions
- Feed Type Overview
- Decision Path
- SmartData Feeds (RWA)
- Rate and Volatility Feeds
- Tokenized Equity Feeds
- Freshness Rules
## Trigger Conditions
Read this file when:
- The user asks about different types of Chainlink Data Feeds
- The user needs help choosing which feed type to use
- The user asks about SmartData, Proof of Reserve, NAV, AUM, or RWA feeds
- The user asks about rate or volatility feeds (Bitcoin Interest Rate, ETH Staking APR, Realized Volatility)
- The user asks about tokenized equity feeds or Ondo Finance feeds
## Feed Type Overview
| Feed Type | Purpose | Interface |
|-----------|---------|-----------|
| **Price Feeds** | Aggregated decentralized asset prices (crypto, forex, commodities, equities) | AggregatorV3Interface |
| **SmartData** | RWA-focused: reserves (PoR), NAV, AUM — single-value and MVR bundle variants | AggregatorV3Interface (single) or IBundleAggregatorProxy (MVR) |
| **SVR Feeds** | Price feeds with MEV/OEV recapture for liquidation-related value | AggregatorV3Interface (same as standard, different address) |
| **Rate and Volatility** | Interest rate curves, staking APR, realized volatility benchmarks | AggregatorV3Interface |
| **L2 Sequencer Uptime** | Real-time sequencer status for circuit breakers on L2 rollups | AggregatorV2V3Interface |
| **Self-Managed** | Updates written by chain/third party, not Chainlink node operators | AggregatorV3Interface (different operational model) |
## Decision Path
1. **Reading an asset price** (ETH/USD, BTC/USD, etc.) → Use a **Price Feed**. See `reading-price-feeds.md`.
2. **Reading multiple related values in one call** (e.g., NAV + AUM + total return) → Use an **MVR Feed**. See `mvr-feeds.md`.
3. **Recapturing MEV from liquidations** → Use an **SVR Feed**. See `svr-feeds.md`.
4. **Interest rates, staking APR, or volatility** → Use a **Rate and Volatility Feed** (see below).
5. **Proof of Reserve, NAV, or AUM** → Use a **SmartData Feed** (see below).
6. **Tokenized equity pricing** → Use a **Tokenized Equity Feed** (see below).
7. **L2 sequencer health** → Use the **L2 Sequencer Uptime Feed**. See `feed-operations.md`.
## SmartData Feeds (RWA)
SmartData provides onchain data for tokenized real-world assets. It includes two variants:
### Single-Value SmartData
Read like standard Price Feeds via AggregatorV3Interface and `latestRoundData()`. Feed categories:
- **Proof of Reserve (PoR)**: confirms reserve backing for asset-backed tokens. Answers may be in non-price units (e.g., ounces, token counts).
- **NAVLink**: Net Asset Value for funds and structured products.
- **SmartAUM**: Assets Under Management.
Sourcing models for PoR:
- **Offchain**: data from custodian, fund administrator, auditor, or asset manager via external adapter.
- **Cross-chain**: reads reserves from the source chain where assets are held.
- **Self-reported**: issuer provides reserve data via API. Higher risk — issuers could manipulate reported reserves by adding addresses they do not control.
### MVR SmartData
Multi-variable response feeds return multiple fields per update. See `mvr-feeds.md` for integration.
### Addresses
SmartData feed addresses: `https://docs.chain.link/data-feeds/smartdata/addresses.md`
Filter using "Show Multiple-Variable Response (MVR) feeds" checkbox for MVR variants.
## Rate and Volatility Feeds
Read rate and volatility feeds exactly like standard Price Feeds — only the feed address differs.
### Bitcoin Interest Rate Curve
- Base rates for lending/borrowing risk, derivatives valuation, and swaps.
- Normalized methodology with daily rates.
- Sources: OTC lending desks, DeFi lending pools, perpetual futures markets.
### ETH Staking APR
- Annualized trust-minimized APR for Ethereum validator staking.
- Computed offchain at epoch level across data providers.
- Available in 30-day and 90-day rolling windows.
- Updates at least once per day.
### Realized Volatility
- Percent of asset price movement over specified intervals (24h, 7d, 30d).
- Not implied volatility — this measures actual historical price movement.
- Providers sample prices every 10 minutes.
- Onchain updates when heartbeat or deviation threshold is met.
### Addresses
Rate and volatility feed addresses: `https://docs.chain.link/data-feeds/rates-feeds/addresses.md`
## Tokenized Equity Feeds
Continuous 24/5 pricing for tokenized representations of US equities and ETFs. These feeds differ significantly from standard crypto price feeds.
### Key characteristics
- **Continuous pricing** across US market sessions: pre-market (04:00-09:30 ET), regular (09:30-16:00), post-market (16:00-20:00), overnight (20:00-04:00).
- **Calculated value**: returns a primary market token value based on issuer methodology, not a simple market price. May include total return adjustments (dividends, corporate actions).
- **Session-aware smoothing**: dampens brief spikes during illiquid sessions but can introduce tracking lag during rapid moves.
- **Variable data quality**: regular session has highest coverage (multiple providers), extended/overnight is limited.
- **Weekend**: no traditional trading — data may be stale.
### Risks
- Limited provider coverage during extended/overnight sessions.
- Price jumps at session transitions.
- Corporate actions trigger pauses in the feed.
- Halts are not explicitly flagged.
### Best practices
- Monitor staleness via last update timestamp.
- Configure protocol risk parameters to match feed behavior.
- Consider restricting high-risk operations (large liquidations, new positions) during extended/overnight sessions.
- Contact Chainlink Labs (`datafeeds@chain.link`) before integrating tokenized equity feeds.
### Ondo Finance (Total Return Value)
Ondo GM feeds report Total Return Value: `Token Price = Underlying Equity Market Price * sValue`
- sValue comes from Ondo's SyntheticSharesOracle (reflects dividend reinvestment and corporate actions).
- Small sValue changes (<=1% per 24h) apply automatically.
- Large sValue changes (>1%) require a scheduled pause and manual confirmation.
- During corporate action pauses, the feed freezes at the last known good token price until unpaused.
- Minimum pause duration enforced (>=10 minutes).
## Freshness Rules
1. Feed type definitions and categories are stable — use this file directly.
2. Specific feed addresses, parameters, and availability by network change — fetch the relevant address page from `official-sources.md`.
3. Tokenized equity feed provider details and issuer-specific behaviors may change — fetch the provider page when the user needs current operational specifics.
references/multi-chain.md
# Multi-Chain Data Feeds (Solana, Aptos, StarkNet, Tron)
## Contents
- Trigger Conditions
- Chain Selection
- Solana
- Aptos
- StarkNet
- Tron
- Freshness Rules
## Trigger Conditions
Read this file when:
- The user wants to read Chainlink Data Feeds on a non-EVM chain
- The user mentions Solana, Aptos, StarkNet, or Tron in a data feeds context
- The user asks about Move, Cairo, Anchor, or TronBox in a data feeds context
For EVM chain integrations, use `reading-price-feeds.md` instead.
## Chain Selection
| Chain | Language | Framework | Feed Model |
|-------|----------|-----------|------------|
| Solana | Rust | Anchor / native Solana | Account-based; use `chainlink_solana` SDK |
| Aptos | Move | Aptos CLI | Single contract queried by feed ID |
| StarkNet | Cairo | Starknet Foundry / Starkli | Cairo contracts on Starknet Sepolia |
| Tron | Solidity | TronBox | Similar to EVM; uses AggregatorV3Interface |
## Solana
### Architecture
Solana feeds are OCR account data read through `chainlink_solana`, not external-chain dependencies. They are available on Mainnet and Devnet (not Testnet); congestion can slow updates.
### On-Chain Reading (Rust / Anchor)
Use the Chainlink Solana SDK v2; validate the account and round before using the value:
```rust
use anchor_lang::{prelude::*, solana_program::pubkey};
use chainlink_solana::v2::read_feed_v2;
const FEED_OWNER: Pubkey =
pubkey!("HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny");
const MAX_AGE: i64 = 3_600; // replace with this feed's heartbeat + buffer
pub fn read_sol_usd(ctx: Context<ReadSolUsd>) -> Result<()> {
let account = &ctx.accounts.feed;
let data = account.try_borrow_data()?;
let feed = read_feed_v2(data, account.owner.to_bytes())
.map_err(|_| error!(FeedError::InvalidFeed))?;
require!(feed.description().starts_with(b"SOL / USD"), FeedError::WrongFeed);
let round = feed.latest_round_data()
.ok_or(error!(FeedError::StaleOrIncomplete))?;
let updated_at = round.timestamp as i64;
let answer = round.answer;
let decimals = feed.decimals();
let now = Clock::get()?.unix_timestamp;
require!(answer > 0, FeedError::InvalidPrice);
require!(
updated_at > 0
&& updated_at <= now
&& now.saturating_sub(updated_at) <= MAX_AGE,
FeedError::StaleOrIncomplete
);
msg!("SOL/USD raw price: {}; decimals: {}", answer, decimals);
Ok(())
}
#[derive(Accounts)]
pub struct ReadSolUsd<'info> {
/// CHECK: owner and SDK decoding are checked before use.
#[account(owner = FEED_OWNER)]
pub feed: UncheckedAccount<'info>,
}
#[error_code]
pub enum FeedError { InvalidFeed, WrongFeed, InvalidPrice, StaleOrIncomplete }
```
`FEED_OWNER` is the OCR2 program, not a feed address. Pass the verified SOL/USD feed account and confirm the current owner, account, identity, and heartbeat in official docs.
### Off-Chain Validation
Validate a decoded SDK round before it drives application logic:
```javascript
function validateRound(round, decimals, now, maxAge) {
const answer = BigInt(round.answer.toString());
const updatedAt = Number(round.timestamp);
if (answer <= 0n) throw new Error("invalid price");
if (!updatedAt || updatedAt > now || now - updatedAt > maxAge)
throw new Error("stale or incomplete round");
return { answer, decimals }; // raw integer
}
```
First verify the feed account's OCR2 owner and description/identity, and obtain `decimals()` at runtime. Never decode the account layout yourself.
## Aptos
### Architecture
Aptos uses a **single Chainlink price feed contract** that serves multiple feeds. Developers query by passing feed ID(s), unlike EVM where each feed has its own contract address.
### Reading a Feed (Move)
```move
use data_feeds::router::get_benchmarks;
public entry fun fetch_price(account: &signer, feed_id: vector<u8>) {
let billing_data = vector::empty<u8>();
let feed_ids = vector::singleton(feed_id);
let benchmarks = get_benchmarks(account, feed_ids, billing_data);
let benchmark = vector::borrow(&benchmarks, 0);
let price = get_benchmark_value(benchmark);
let timestamp = get_benchmark_timestamp(benchmark);
// Store or use price and timestamp
}
```
Example BTC/USD testnet feed ID: `0x01a0b4d920000332000000000000000000000000000000000000000000000000`
## StarkNet
### Architecture
StarkNet is non-EVM; smart contracts use Cairo. Chainlink Data Feeds are deployed as Cairo contracts on Starknet Sepolia.
### Off-Chain Reading (Starkli CLI)
No Starknet account required for reads:
```bash
starkli call \
0x228128e84cdfc51003505dd5733729e57f7d1f7e54da679474e73db4ecaad44 \
latest_round_data \
--rpc https://starknet-sepolia.public.blastapi.io/rpc/v0_7
```
Returns a hex array: `[round_id, answer, block_num, started_at, updated_at]`.
Any Starknet Sepolia RPC provider works (Blast API, Alchemy, Infura).
Example ETH/USD proxy: `0x228128e84cdfc51003505dd5733729e57f7d1f7e54da679474e73db4ecaad44`
### Off-Chain Reading (Starknet Foundry)
```bash
sncast --url <RPC> call \
--contract-address 0x228128e84cdfc51003505dd5733729e57f7d1f7e54da679474e73db4ecaad44 \
--function "latest_round_data"
```
### On-Chain Model
Onchain consumers are Cairo contracts that call the Starknet aggregator proxy; unlike read-only `starkli`/`sncast` calls, deployment requires a funded Starknet account. The official `chainlink-starknet/examples/contracts/aggregator_consumer/` example covers Sepolia and local Devnet RS.
## Tron
### Architecture
Tron uses Solidity-compatible smart contracts with AggregatorV3Interface — similar to EVM chains. Deploy with TronBox.
### Consumer Contract (Solidity on Tron)
The contract uses the same AggregatorV3Interface as EVM:
```solidity
// DataFeedReader.sol
import {AggregatorV3Interface} from "@chainlink/contracts/...";
function getLatestPrice() public view returns (int256) {
(, int256 price, , , ) = dataFeed.latestRoundData();
return price;
}
```
Test feed addresses (Nile testnet):
- BTC/USD: `TD3hrfAtPcnkLSsRh4UTgjXBo6KyRfT1AR`
- ETH/USD: `TYaLVmqGzz33ghKEMTdC64dUnde5LZc6Y3`
Note: Tron uses base58 addresses, not hex.
## Freshness Rules
1. Integration patterns (SDK versions, CLI commands, code patterns) are stable within major versions — use this file directly.
2. Feed addresses, program IDs, and RPC endpoints may change — fetch the chain-specific docs page for current values when the user needs a specific address.
3. Package/dependency versions may update — verify against the docs page if the user reports compilation errors with versions in this file.
references/mvr-feeds.md
# MVR Bundle Feeds (Multiple-Variable Response)
Use for Chainlink MVR/`IBundleAggregatorProxy` reads. MVR packs several typed fields into `bytes`; decode the exact documented struct/tuple. Single-value prices, SVR, and CCIP use other workflows.
## MVR Architecture
MVR feeds pack multiple typed fields (uint256, bool, etc.) into a single `bytes` bundle stored onchain via a `BundleAggregatorProxy`. Only the latest bundle is stored; there is no `getRoundData` equivalent for historical lookups. Historical data requires your own contract storage or an offchain indexer.
Core interface: `IBundleAggregatorProxy`
Import: `@chainlink/contracts/src/v0.8/data-feeds/interfaces/IBundleAggregatorProxy.sol`
Key functions:
- `latestBundle() returns (bytes memory)` -- raw bundle bytes
- `bundleDecimals() returns (uint8[] memory)` -- numeric scaling; validate the exact schema length before indexing, and never treat a boolean's position as scaling metadata
- `latestBundleTimestamp() returns (uint256)` -- block timestamp of the most recent update
- `description() returns (string memory)` -- human-readable feed description
- `version() returns (uint256)` -- feed version
Find the proxy address and exact bundle schema on the SmartData Addresses page. Filter with "Show Multiple-Variable Response (MVR) feeds" and open "MVR Bundle Info" for the target feed.
## Solidity Consumer Pattern
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {IBundleAggregatorProxy} from
"@chainlink/contracts/src/v0.8/data-feeds/interfaces/IBundleAggregatorProxy.sol";
contract MVRConsumer {
IBundleAggregatorProxy internal immutable bundleFeed;
uint256 public immutable maxAge; // feed heartbeat + buffer
error InvalidTimestamp();
error StaleData();
error SchemaMismatch();
// MUST match the documented schema's order and types.
struct FundData {
uint256 totalReturn;
uint256 nav;
uint256 aum;
bool openToNewInvestors;
}
constructor(address proxyAddress, uint256 maxAgeSeconds) {
require(proxyAddress != address(0) && maxAgeSeconds != 0);
bundleFeed = IBundleAggregatorProxy(proxyAddress);
maxAge = maxAgeSeconds;
}
function getLatestData()
external
view
returns (FundData memory data, uint8[3] memory numericDecimals)
{
uint256 updatedAt = bundleFeed.latestBundleTimestamp();
if (updatedAt == 0 || updatedAt > block.timestamp) {
revert InvalidTimestamp();
}
if (block.timestamp - updatedAt > maxAge) revert StaleData();
uint8[] memory decimals = bundleFeed.bundleDecimals();
if (decimals.length != 4) revert SchemaMismatch();
numericDecimals = [decimals[0], decimals[1], decimals[2]];
data = abi.decode(bundleFeed.latestBundle(), (FundData));
// The bool is used directly; it is not scaled or decimals[3] metadata.
}
}
```
Critical rules for Solidity consumers:
1. The struct field order and types MUST exactly match the feed's documented schema. Mismatched order silently produces wrong values.
2. Set `maxAge` to the feed's heartbeat plus a small buffer, not an arbitrary value.
3. `uint256` division truncates. Keep raw integers onchain and scale offchain when precision matters.
4. Check `bundleDecimals().length` before indexing; scale numeric fields only. Booleans are used directly.
5. On an L2 rollup, the consumer must also apply the L2 sequencer uptime check.
## Schema-Neutral Off-Chain Sequence
Use the caller's current EVM client; the sequence is the same for ethers, viem, web3, or raw RPC:
1. Get the `BundleAggregatorProxy` address and exact ordered field types from the SmartData Addresses page's **MVR Bundle Info**. Never infer a schema from field names.
2. Bind the proxy with the minimal `IBundleAggregatorProxy` reads: `latestBundle() returns (bytes)`, `bundleDecimals() returns (uint8[])`, and `latestBundleTimestamp() returns (uint256)`.
3. Compare `latestBundleTimestamp()` with current Unix time using the feed heartbeat plus a buffer.
4. Fetch `latestBundle()` and ABI-decode it with the exact documented field order and types.
5. Fetch `bundleDecimals()`; map entries by schema index, format numeric fields with their own precision, and leave non-numeric fields unscaled.
6. Preserve raw integers when downstream precision matters. Client-specific helpers include ethers `AbiCoder`/`formatUnits` and viem `decodeAbiParameters`/`formatUnits`.
## Freshness
Verify the current proxy, exact schema, and heartbeat before generating code. MVR stores only the latest bundle and does not support `latestRoundData()`/`getRoundData()`; persist bundles or index them offchain for history.
references/official-sources.md
# Official Sources
Use live sources only when embedded references lack a current address, deprecation state, network parameter, signature, or implementation detail.
## Freshness and destination matrix
| Need | Official destination | Use |
|---|---|---|
| Concepts, architecture, interfaces, feed types, responsibilities | `https://docs.chain.link/data-feeds.md` | Stable documentation; prefer curated references for ordinary integration |
| Price feed addresses | `https://docs.chain.link/data-feeds/price-feeds/addresses.md` | Network/pair proxy, heartbeat, deviation, decimals |
| SmartData and MVR addresses/schemas | `https://docs.chain.link/data-feeds/smartdata/addresses.md` | Proxy and exact **MVR Bundle Info** field order/types |
| Rate/volatility addresses | `https://docs.chain.link/data-feeds/rates-feeds/addresses.md` | Current network availability and parameters |
| U.S. government macro addresses | `https://docs.chain.link/data-feeds/us-government-macroeconomic/addresses.md` | Current macro feed directory |
| Deprecations | `https://docs.chain.link/data-feeds/deprecating-feeds.md` | Dates, networks, replacement guidance |
| Registry | `https://docs.chain.link/data-feeds/contract-registry.md` | Network `IFlags` address and active official proxies |
| Broad fallback | `https://docs.chain.link/data-feeds/llms-full.txt` | Last resort only; prefer the smallest page above |
Re-check live sources for specific addresses, deprecations, availability, and network configuration. Prefer a conflicting live source over cached data and identify the change. Do not use documentation prose as a substitute for a feed's address/schema table.
## Source and example fetch matrix
Fetch one exact file when debugging an interface mismatch, proxy behavior, signature, return type, or implementation. Use the embedded integration references instead of browsing source for routine answers, and use address pages—not repositories—for live configuration.
### `smartcontractkit/documentation`
Base: `https://github.com/smartcontractkit/documentation/blob/main/`
| Shape | Paths under the base |
|---|---|
| EVM consumers | `public/samples/DataFeeds/DataConsumerV3.sol`; `DataConsumerWithSequencerCheck.sol`; `PriceConverter.sol`; `HistoricalDataConsumer.sol`; `ReserveConsumerV3.sol`; `ENSConsumer.sol` |
| Offchain EVM | `public/samples/DataFeeds/PriceConsumerV3.js`; `PriceConsumerV3Ethers.js`; `PriceConsumerV3.py`; `HistoricalDataConsumer.js`; `HistoricalDataConsumer.py`; `ENSConsumer.js` |
| MVR | `public/samples/DataFeeds/MVR/MVRDataConsumer.sol` |
| SVR searcher code | `public/samples/DataFeeds/SVR/broadcaster.go`; `broadcaster.ts`; `decoder.go`; `decoder.ts`; `listener.go`; `listener.ts` |
| SVR payload/decoding data | `public/samples/DataFeeds/SVR/bundle-bid.json`; `bundle-transaction-event.json`; `single-transaction-event.json`; `decoding-abi.json` |
| Solana readers | `public/samples/Solana/PriceFeeds/on-chain-read.rs`; `on-chain-read-anchor.rs`; `off-chain-read.js`; `off-chain-read.ts` |
Repository directory: `https://github.com/smartcontractkit/documentation/tree/main/public/samples/DataFeeds`
### `smartcontractkit/chainlink-evm`
Base: `https://github.com/smartcontractkit/chainlink-evm/blob/develop/`
| Question | Paths under the base |
|---|---|
| Upgradeable price proxy behavior | `contracts/src/v0.6/data-feeds/AggregatorProxy.sol`; `EACAggregatorProxy.sol` |
| Price/sequencer interfaces | `contracts/src/v0.6/data-feeds/interfaces/AggregatorV3Interface.sol`; `AggregatorV2V3Interface.sol`; legacy `AggregatorInterface.sol` |
| MVR proxy/cache | `contracts/src/v0.8/data-feeds/BundleAggregatorProxy.sol`; `DataFeedsCache.sol` |
| MVR interfaces | `contracts/src/v0.8/data-feeds/interfaces/IBundleAggregatorProxy.sol`; `IBundleAggregator.sol`; `ICommonAggregator.sol` |
Repository directories: legacy `https://github.com/smartcontractkit/chainlink-evm/tree/develop/contracts/src/v0.6/data-feeds`; MVR `https://github.com/smartcontractkit/chainlink-evm/tree/develop/contracts/src/v0.8/data-feeds`.
## Fetch selection
- Interface mismatch: fetch the specific interface and compare its function signature to the caller.
- Proxy delegation: fetch `AggregatorProxy.sol` or `EACAggregatorProxy.sol`; consumers still read the proxy.
- Runnable example: fetch only the matching sample file.
- MVR internals: fetch `BundleAggregatorProxy.sol` or `IBundleAggregatorProxy.sol` after confirming the live schema on SmartData Addresses.
- Unknown live fact: do not guess; name and fetch the smallest URL in the destination matrix.
references/reading-price-feeds.md
# Reading Price Feeds
## Contents
- Trigger Conditions
- Core Interface
- Solidity Consumer Pattern
- L2 Sequencer Uptime Check
- Off-Chain Reading
- Historical Data
- Feed Address Lookup
- Debugging
- Common Errors
- Freshness Rules
Use this file for any request that involves reading Chainlink price feed data on EVM chains, whether onchain via Solidity or offchain via JavaScript/Python.
For price-reading implementation requests, emit the requested Solidity consumer or runnable Node.js script—not a description of one. Direct the user to verify the address on the official Chainlink Feed Addresses page (and registry when applicable), and include runtime `decimals()`, answer bounds, incomplete/future/stale `updatedAt` rejection, and applicable install, configuration, deployment, and run steps.
## Trigger Conditions
Use this workflow for requests like:
- "I need to read the ETH/USD price in my Solidity contract."
- "How do I get a Chainlink price feed in ethers.js?"
- "Add a staleness check to my price feed consumer."
- "My protocol runs on Arbitrum. How do I safely read a price feed?"
- "Show me how to fetch historical price data from Chainlink."
Do not use this workflow for non-EVM chains (Solana, StarkNet, Aptos), MVR/bundle feeds, Data Streams, or CCIP.
## Core Interface
All EVM price feeds expose `AggregatorV3Interface` through a proxy contract.
Import path:
```solidity
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
```
Functions:
- `decimals() -> uint8` -- number of decimal places in the answer
- `description() -> string` -- human-readable pair name (e.g., "ETH / USD")
- `version() -> uint256` -- interface version of the aggregator
- `latestRoundData() -> (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)`
- `getRoundData(uint80 _roundId)` -- same return tuple, for historical queries
Important notes:
1. `answeredInRound` is deprecated. Do not use it for freshness checks.
2. Always read through the proxy contract, never directly from the underlying aggregator. Aggregators can be upgraded behind the proxy; reading the proxy keeps your integration stable.
## Solidity Consumer Pattern
Full working consumer with all required validation:
For an implicit current-price getter, preserve the exact named-return shape shown below, including predeclaring `updatedAt` and assigning `(, price,, updatedAt,) = dataFeed.latestRoundData();`; do not substitute an unnamed return or alternate compiling tuple form.
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
contract DataConsumerV3 {
AggregatorV3Interface internal immutable dataFeed;
uint256 public immutable maxAge;
constructor(address feedAddress, uint256 maxAgeSeconds) {
require(feedAddress != address(0), "Invalid feed");
require(maxAgeSeconds != 0, "Invalid max age");
dataFeed = AggregatorV3Interface(feedAddress);
maxAge = maxAgeSeconds; // feed heartbeat + buffer
}
function getLatestPrice()
external
view
returns (int256 price, uint8 decimals)
{
uint256 updatedAt;
(, price,, updatedAt,) = dataFeed.latestRoundData();
require(updatedAt != 0, "Round not complete");
require(updatedAt <= block.timestamp, "Future timestamp");
require(block.timestamp - updatedAt <= maxAge, "Stale price");
require(price > 0, "Invalid price");
decimals = dataFeed.decimals();
}
}
```
### Validation Requirements
Every consumer must include these checks:
1. **Staleness**: reject zero or future `updatedAt` before subtracting, then require `block.timestamp - updatedAt <= maxAge`. Constructor-configure `maxAge` from this feed's heartbeat plus a buffer; feeds differ.
2. **Answer bounds**: `require(answer > 0)` at minimum. For production, consider tighter min/max bounds appropriate for the specific asset.
3. **Round completeness**: `require(updatedAt > 0)`. A zero `updatedAt` indicates the round has not completed.
4. **Decimals**: Always call `decimals()` at runtime or store the result in an immutable. Never hardcode 8. ETH/USD uses 8 decimals, but other feeds (especially non-USD quote pairs) may use 18.
## L2 Sequencer Uptime Check
Required on Arbitrum, Optimism, Base, Scroll, and all other L2 rollups with a sequencer. If the sequencer is down, price feeds on the L2 are not updated and stale data can cause incorrect liquidations or trades.
The sequencer uptime feed uses `AggregatorV2V3Interface` (not `AggregatorV3Interface`).
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
import {AggregatorV2V3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV2V3Interface.sol";
contract DataConsumerWithSequencerCheck {
AggregatorV3Interface internal immutable dataFeed;
AggregatorV2V3Interface internal immutable sequencerUptimeFeed;
uint256 public immutable maxAge;
uint256 public constant GRACE_PERIOD_TIME = 3600;
error SequencerDown();
error InvalidSequencerTimestamp();
error GracePeriodNotOver();
error InvalidPriceTimestamp();
constructor(
address feedAddress,
address sequencerFeedAddress,
uint256 maxAgeSeconds
) {
require(
feedAddress != address(0)
&& sequencerFeedAddress != address(0)
&& maxAgeSeconds != 0,
"Invalid config"
);
dataFeed = AggregatorV3Interface(feedAddress);
sequencerUptimeFeed = AggregatorV2V3Interface(sequencerFeedAddress);
maxAge = maxAgeSeconds;
}
function getLatestPrice()
external
view
returns (int256, uint8)
{
(, int256 status, uint256 startedAt,,) =
sequencerUptimeFeed.latestRoundData();
if (status != 0) revert SequencerDown();
if (startedAt == 0 || startedAt > block.timestamp) {
revert InvalidSequencerTimestamp();
}
if (block.timestamp - startedAt < GRACE_PERIOD_TIME) {
revert GracePeriodNotOver();
}
(, int256 answer,, uint256 updatedAt,) = dataFeed.latestRoundData();
if (updatedAt == 0 || updatedAt > block.timestamp) {
revert InvalidPriceTimestamp();
}
require(block.timestamp - updatedAt <= maxAge, "Stale price");
require(answer > 0, "Invalid price");
return (answer, dataFeed.decimals());
}
}
```
Key details:
- `answer == 0` means the sequencer is up. `answer == 1` means it is down.
- `startedAt` from the uptime feed is the timestamp when the sequencer status last changed.
- After sequencer recovery, enforce a grace period (typically 3600 seconds) before trusting price data. This prevents acting on prices that may be stale or manipulated immediately after the sequencer resumes.
- Sequencer uptime feed proxy addresses are network-specific. Check the official L2 Sequencer Uptime Feeds documentation for the correct address per chain.
## Off-Chain Reading
### ethers.js
```javascript
const { ethers } = require("ethers");
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
const ABI = [
"function latestRoundData() external view returns (uint80, int256, uint256, uint256, uint80)",
"function decimals() external view returns (uint8)",
];
const priceFeed = new ethers.Contract(FEED_ADDRESS, ABI, provider);
async function getPrice(maxAge) { // heartbeat + buffer for this feed
const [roundId, answer, startedAt, updatedAt, answeredInRound] =
await priceFeed.latestRoundData();
const decimals = await priceFeed.decimals();
const now = Math.floor(Date.now() / 1000);
const timestamp = updatedAt.toNumber();
if (!timestamp || timestamp > now || now - timestamp > maxAge) {
throw new Error("Stale or incomplete price data");
}
if (answer.lte(0)) throw new Error("Invalid price");
console.log(`Price: ${ethers.utils.formatUnits(answer, decimals)}`);
}
```
### web3.js
```javascript
const Web3 = require("web3");
const web3 = new Web3(RPC_URL);
const ABI = [
/* AggregatorV3Interface ABI JSON array */
];
const priceFeed = new web3.eth.Contract(ABI, FEED_ADDRESS);
async function getPrice() {
const roundData = await priceFeed.methods.latestRoundData().call();
const decimals = await priceFeed.methods.decimals().call();
console.log(`Price: ${roundData.answer} (${decimals} decimals)`);
}
```
### Python (web3.py)
```python
from web3 import Web3
w3 = Web3(Web3.HTTPProvider(RPC_URL))
abi = [...] # AggregatorV3Interface ABI JSON array
contract = w3.eth.contract(address=FEED_ADDRESS, abi=abi)
round_data = contract.functions.latestRoundData().call()
decimals = contract.functions.decimals().call()
# round_data is a tuple: (roundId, answer, startedAt, updatedAt, answeredInRound)
answer = round_data[1]
updated_at = round_data[3]
print(f"Price: {answer / 10**decimals}")
```
## Historical Data
Use `getRoundData(uint80 roundId)` to query past rounds. The proxy roundId encodes both phase and aggregator round:
```
proxyRoundId = (phaseId << 64) | aggregatorRoundId
```
- `phaseId` increments each time the proxy upgrades to a new underlying aggregator.
- `aggregatorRoundId` starts at 1 for each new aggregator.
- Extract phase: `phaseId = roundId >> 64`
- Extract aggregator round: `aggregatorRoundId = uint64(roundId)`
Important caveats:
1. Looping through rounds on-chain is extremely gas-expensive. Prefer off-chain queries for historical data.
2. Round IDs are not guaranteed to be continuous. You must know a valid roundId.
3. A zero `updatedAt` timestamp indicates an incomplete round.
4. When iterating a phase, start at `(phaseId << 64) | 1` and increment until `getRoundData` reverts, which signals the end of that aggregator's history.
## Feed Address Lookup
- Price feed addresses by network: `https://docs.chain.link/data-feeds/price-feeds/addresses.md`
- SmartData feed addresses: `https://docs.chain.link/data-feeds/smartdata/addresses.md`
- Contract registry (IFlags): use `getFlag(proxyAddress)` to verify a feed is official and active on-chain. Flags contract addresses are network-specific.
Common testnet examples (Sepolia):
| Pair | Address | Decimals |
|---------|----------------------------------------------|----------|
| BTC/USD | `0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43` | 8 |
| ETH/USD | `0x694AA1769357215DE4FAC081bf1f309aDC325306` | 8 |
Always verify addresses against the official documentation before use. Feed addresses change across networks and can be deprecated.
## Debugging
For a failing consumer, return the complete applicable consumer above—not a fragment with undeclared feeds, thresholds, or errors—and map the observed failure:
- `SequencerDown`: sequencer status is nonzero; wait for recovery.
- `InvalidSequencerTimestamp` / `GracePeriodNotOver`: status time is zero/future, or the 3600-second grace period is active.
- `InvalidPriceTimestamp` / `Stale price`: incomplete/future round, wrong max-age, deprecated feed, or wrong proxy/network.
- `Invalid price`: non-positive answer or unsuitable feed; `latestRoundData()` reverting usually means a wrong address/network or non-feed contract.
## Common Errors
1. **Using AggregatorV3Interface for the L2 sequencer uptime feed.** The sequencer feed requires `AggregatorV2V3Interface`. Using the wrong interface will cause compilation errors or missing data.
2. **Not checking `updatedAt` for staleness.** Without a staleness check, your contract can act on arbitrarily old prices during network congestion, sequencer downtime, or feed deprecation.
3. **Hardcoding 8 decimals.** Most USD-quoted feeds use 8 decimals, but this is not universal. Always call `decimals()`.
4. **Using `answeredInRound` for freshness checks.** This field is deprecated and should not be used for any logic.
5. **Not checking the sequencer on L2 chains.** On rollups, the sequencer being down means feeds are not updated. Skipping the sequencer check can lead to acting on stale or manipulable prices.
6. **Using a deprecated or incorrect feed address.** Feed addresses are network-specific and can be deprecated. Always verify against the official address list and consider checking the on-chain Flags contract registry.
## Freshness Rules
1. Feed addresses, heartbeat intervals, and deviation thresholds change over time. When generating code for a specific network and pair, verify the address and parameters against the official feed address pages.
2. The `AggregatorV3Interface` and `AggregatorV2V3Interface` contract interfaces are stable. Code patterns using these interfaces do not require re-verification.
3. L2 sequencer uptime feed proxy addresses are network-specific and should be verified against the official L2 Sequencer Uptime Feeds documentation.
4. When in doubt about whether a feed is still active, check the deprecation schedule at `https://docs.chain.link/data-feeds/deprecating-feeds.md`.
references/svr-feeds.md
# SVR Feeds (Smart Value Recapture)
## Contents
- Trigger Conditions
- SVR Architecture
- Protocol Integration
- Searcher Onboarding — Ethereum (MEV-Share)
- Searcher Onboarding — Atlas (Base, Arbitrum, BNB Chain)
- Freshness Rules
## Trigger Conditions
Read this file when:
- The user asks about Smart Value Recapture or SVR feeds
- The user mentions OEV (oracle-extractable value) or MEV recapture
- The user wants to integrate SVR feeds into a DeFi protocol
- The user is a searcher wanting to participate in SVR auctions
Every SVR answer must emit the exact standalone sentence `Complete the Chainlink compatibility form` before any protocol-onboarding or address-migration guidance.
For a configuration-focused protocol integration or migration, emit concise configuration, onboarding, verification, and safety steps plus only the minimal address/configuration delta; do not emit a full consumer unless the user explicitly requests one. Preserve the user's price-feed consumer as `AggregatorV3Interface`. An L2 sequencer uptime feed always uses `AggregatorV2V3Interface`, never `AggregatorV3Interface`. Explain the auction and fail-safe at a high level and include applicable staleness, runtime-decimals, answer-bound, sequencer grace-period, audit, monitoring, and production-responsibility safety steps. Include searcher-only selectors, WebSocket endpoints, signing instructions, or MEV-Share/Atlas bot implementation details only when the user explicitly asks to build or operate a searcher.
## SVR Architecture
SVR extends standard Chainlink Price Feeds to recapture oracle-extractable value — primarily non-toxic liquidation-related MEV — via an optional private transmission route and auction.
### How it works
1. **Dual transmission**: Each price update is sent through two paths:
- Standard Aggregator: transmitted via the public mempool (normal path)
- SVR Aggregator: transmitted via a private channel (e.g., Flashbots MEV-Share on Ethereum, Atlas on Base/Arbitrum/BNB Chain)
2. **Auction**: Searchers bid to backrun the oracle update with a liquidation. The builder selects the highest bid and bundles the liquidation in the same block.
3. **Fail-safe**: If the private route fails or times out, the SVR feed reverts to the Standard Feed price after a configurable delay.
4. **Revenue split**: Recaptured OEV is split between the integrating protocol and the Chainlink Network. The split may change over time.
### Supported auction systems by network
- **Ethereum Mainnet**: Flashbots MEV-Share
- **Base, Arbitrum, BNB Chain**: Atlas
## Protocol Integration
Integrating SVR feeds is normally a configuration-only migration: the price-feed interface remains `AggregatorV3Interface`, and the configured proxy address changes to the verified SVR feed address.
### Steps
1. `Complete the Chainlink compatibility form`
2. Complete protocol onboarding before integration.
3. Find and verify the network-specific SVR proxy on the official Feed Addresses page.
4. Change only the configured price-feed proxy/source from the standard feed to that verified SVR proxy.
5. Verify the deployed configuration points to the intended SVR proxy and exercise fresh, bounded reads using runtime `decimals()`.
6. On L2, verify sequencer status first and retain the 3600-second recovery grace period.
7. Deploy through the protocol's normal governance/change process and monitor feed health, auction latency, and fail-safe behavior.
All standard validation applies: staleness checks, bounds checks, runtime decimals, and L2 sequencer checks when applicable. L2 sequencer uptime feeds always use `AggregatorV2V3Interface`, never `AggregatorV3Interface`.
### Minimal configuration delta (default)
```diff
- priceFeedProxy: <verified-standard-feed-proxy>
+ priceFeedProxy: <verified-svr-feed-proxy>
```
Keep the interfaces distinct:
```solidity
AggregatorV3Interface public immutable svrFeed;
AggregatorV2V3Interface public immutable sequencerUptimeFeed;
```
### Validated full consumer (only when explicitly requested)
```solidity
import {AggregatorV3Interface} from
"@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
contract SVRConsumer {
AggregatorV3Interface public immutable svrFeed;
uint256 public immutable maxAge;
int256 public immutable minPrice;
int256 public immutable maxPrice;
constructor(address proxy, uint256 age, int256 min, int256 max) {
require(proxy != address(0) && age != 0, "Invalid config");
require(min > 0 && max > min, "Invalid bounds");
svrFeed = AggregatorV3Interface(proxy);
maxAge = age;
minPrice = min;
maxPrice = max;
}
function latestPrice() external view returns (int256, uint8) {
(, int256 answer,, uint256 updatedAt,) =
svrFeed.latestRoundData();
require(updatedAt != 0 && updatedAt <= block.timestamp, "Invalid round");
require(block.timestamp - updatedAt <= maxAge, "Stale price");
require(answer >= minPrice && answer <= maxPrice, "Price out of bounds");
return (answer, svrFeed.decimals());
}
}
```
Set `maxAge` from the feed heartbeat and configure asset-specific bounds. On an L2, perform the sequencer check before this read with `AggregatorV2V3Interface`—never `AggregatorV3Interface`—and enforce the 3600-second recovery grace period.
### Risks to communicate
- SVR introduces a delay (private route auction adds latency vs standard public mempool)
- Liquidation competition still exists — searchers compete in the auction
- MEV is not eliminated, only partially recaptured
- Recapture rates are dynamic and may vary
## Searcher Onboarding — Ethereum (MEV-Share)
### Flow
1. **Monitor** the Flashbots MEV-Share private mempool for SVR price update transactions.
2. **Filter** pending tx events by the `forward()` function selector `0x6fadcf72`.
3. **Decode** the nested calldata: `forward(address to, bytes callData)` contains an encoded `transmitSecondary(bytes32[3] reportContext, bytes report, bytes32[] rs, bytes32[] ss, bytes32 rawVs)` call.
4. **Extract** the updated feed address and median price from the report bytes: decode to `(uint32 observationsTimestamp, bytes32 observers, int192[] observations, int192 juelsPerFeeCoin)`.
5. **Construct** a liquidation transaction to backrun the price update in the same bundle.
6. **Submit** the bundle to MEV-Share with your bid.
### Key details
- Price updates come from multiple forwarder contracts (per Node Operator Proxy) — updates may originate from different addresses.
- MEV-Share can emit single-transaction events or bundle events; bundle txs are in ascending nonce order.
- Searcher submits liquidation tx in same bundle as price update; highest bid wins.
- Contact: svr@chain.link
## Searcher Onboarding — Atlas (Base, Arbitrum, BNB Chain)
### Flow
1. **Connect** to the SVR bid endpoint WebSocket: `wss://svr-bid-endpoint.chain.link/ws/solver`
2. **Receive** user operations (oracle updates) as EIP-712 messages.
3. **Simulate** solver operations locally — failed sims never hit chain.
4. **Sign** the required payloads: EIP-191 message format `<auctionID>:<userOperationHash>:<solverOperationFrom>` (colon-delimited).
5. **Submit** the solution. The final bundled transaction is submitted by Chainlink oracles, not the searcher.
### Key details
- Bond native tokens with Atlas contracts for gas reimbursement: Base/Arbitrum 0.1 ETH, BNB Chain 1 BNB.
- The gas price is chosen by the Chainlink oracle and provided at auction start. Solver must sign gasPrice exactly equal to provided value.
- Do not exceed `SolverGasLimit` (query via `DappControl.getSolverGasLimit`).
- Set WebSocket read/write buffers > 10KB and implement auto-reconnect.
- Listen to the **aggregator** address for price reports, not the proxy. Get aggregator via `proxy.aggregator()`.
- Use Aave-SVR feeds for Aave and SVR feeds for other protocols.
- Contact: svr@chain.link
## Freshness Rules
1. SVR feed addresses and supported networks may change — fetch the address page when the user asks for a specific SVR feed address.
2. Auction mechanics and Atlas contract addresses may be updated — fetch the searcher onboarding page when the user needs current operational details.
3. The integration pattern (AggregatorV3Interface with SVR address) is stable and can be used from this file directly.
SKILL.md
---
name: chainlink-data-feeds-skill
description: "Helps developers read Chainlink price feeds with AggregatorV3Interface and latestRoundData. This is not a Data Streams report. REST and WebSocket report streams go to chainlink-data-streams-skill. It applies to price feed integration, feed address lookup, consumer contracts, multi-chain feeds, MVR bundle feeds, SVR and OEV feeds, feed monitoring, historical data, L2 sequencer checks, rates and volatility feeds, and SmartData and RWA feeds."
license: MIT
allowed-tools: Read WebFetch Write Edit
metadata:
version: "0.0.9"
---
# Chainlink Data Feeds Skill
## Progressive Disclosure
| Request / triggers | Load |
|---|---|
| EVM read, consumer contract, off-chain read, address lookup/debugging | [price feeds](references/reading-price-feeds.md) |
| Working Foundry/Data Feeds project | Load the [starter README](templates/starter-kit/README.md) and every listed file; reply with their contents or a tailored derivative, never a local/absolute path alone. Preserve layout and include install/test/deploy commands, Sepolia ETH/USD or verified named-network lookup, unaudited/not-for-production warning, and L2 sequencer protection. |
| Multiple-Variable Response, multi-variable feed, `BundleAggregatorProxy` | [MVR](references/mvr-feeds.md) |
| Smart Value Recapture, OEV/MEV recapture, searcher onboarding | [SVR](references/svr-feeds.md) |
| Feed categories/selection, SmartData/RWA, rates/volatility, tokenized equity | [types](references/feed-types.md) |
| Solana, StarkNet, Aptos, Tron, Move, Cairo, Anchor, TronBox | [multi-chain](references/multi-chain.md) |
| L2 sequencer, deprecation, monitoring, registry, responsibilities, data sources, self-managed feeds | [operations](references/feed-operations.md) |
| Live addresses/schedules/parameters; interface/signature mismatch, proxy/aggregator source, GitHub example | [official sources](references/official-sources.md), then [docs index](assets/data-feeds-docs-index.md) for facts the curated references do not have |
Do not load references speculatively. Default EVM requests to price feeds; ask only when routing depends on missing context.
Do not assume this skill is the only capability available.
Hand CCIP/bridge, VRF, Automation, and Functions requests to their owning products exclusively, giving each product's standard next steps from general knowledge; emit no Data Feeds or weaker substitute and never improvise live constants.
When a user requests Data Feeds code or operational steps, emit that deliverable in the response; never replace it with a status or completion summary.
For MVR requests, choose the MVR route even when a live address, feed ID, or schema cannot be verified; use clearly marked placeholders for unverified values and continue with all requested design, consumer code, tests, and configuration instead of stopping at verification.
Every SVR response must emit the exact standalone sentence `Complete the Chainlink compatibility form` before any onboarding or address-migration guidance.
For a configuration-focused SVR protocol integration or migration, give concise configuration, onboarding, verification, and safety steps plus only the minimal address/configuration delta; do not emit a full consumer unless the user explicitly requests one. Keep auction mechanics high-level and exclude searcher-only selectors, WebSocket endpoints, signing instructions, and MEV-Share/Atlas bot implementation details unless the user explicitly asks to build or operate a searcher.
## Safety rules
1. Validate `updatedAt` against the feed heartbeat; reject incomplete or stale rounds.
2. Call `decimals()` at runtime; never hardcode decimals.
3. On L2, always use `AggregatorV2V3Interface` for the sequencer uptime feed—never `AggregatorV3Interface`; reject `answer != 0` and enforce the 3600-second recovery grace period.
4. Never use deprecated `answeredInRound` for freshness or other logic.
5. For an implicit current-price Solidity getter, use the exact named-return form `returns (int256 price, uint8 decimals)`; declare `uint256 updatedAt;` first, then assign `(, price,, updatedAt,) = dataFeed.latestRoundData();`. Do not substitute an unnamed-return or alternate compiling tuple form.
6. Warn that examples are unaudited and not for production; mainnet integrators own audits, dependencies, monitoring, circuit breakers, contingencies, and user risk disclosure.
## Freshness policy
1. Use this skill's references first.
2. If [official-sources.md](references/official-sources.md) names the page, fetch that page's `.md` URL.
3. If it does not, fetch `https://docs.chain.link/data-feeds/llms.txt`, pick one page, and fetch that page's `.md` URL.
4. Fetch `https://docs.chain.link/data-feeds/llms-full.txt` only when the question is broad and no one page covers it.
5. Use Context7 only when the `.md` fetch fails, the page is empty, or the page has under 1,000 useful characters and does not answer. Ask for the missing fact only.
6. Stop when the page answers. Fetch at most three pages. Prefer the `.md` URL. Use `Accept: text/markdown` on the normal page URL only when the tool cannot open the `.md` URL and can set that header.
7. Never improvise live facts or patterns. If a value is unverified, say so and name the URL.
## Invariants
- Read EVM proxies, not underlying aggregators.
- Preserve the starter kit layout/README invariants; replace illustrations and tune heartbeat/mock values.
- Use `AggregatorV3Interface` for single values, `IBundleAggregatorProxy` for MVR, and `AggregatorV2V3Interface` for sequencer uptime.
- A live feed address in config must be copied from the official address page fetched in this turn. When that page lists more than one contract for the pair, use the contract for the product the user asked for. A normal price read uses the standard proxy, not an SVR, backup, or legacy proxy. If the page was not fetched, use a placeholder and say the address is unverified. Never present a remembered address as the verified current feed.
- Verify unknown feed IDs, schemas, parameters, and support.
templates/starter-kit/foundry.toml
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
optimizer = true
optimizer_runs = 20000
templates/starter-kit/README.md
# Chainlink Data Feeds Foundry Starter Kit Template
Use this template when a user asks for a working Data Feeds project, a Foundry starter kit, or a runnable example. It is based on the Chainlink Foundry Starter Kit `PriceFeedConsumer.sol` pattern and adds the skill's required validation: round completion, staleness, positive answer checks, and runtime decimals access.
## Files
```text
foundry.toml
remappings.txt
src/PriceFeedConsumer.sol
script/PriceFeedConsumer.s.sol
test/PriceFeedConsumer.t.sol
test/mocks/MockV3Aggregator.sol
```
## Dependencies
For a fresh Foundry project, install these dependencies before running the template:
```sh
forge install foundry-rs/forge-std
forge install smartcontractkit/chainlink-evm@contracts-v1.5.0
```
The included `remappings.txt` expects Chainlink contracts under `lib/chainlink-evm/` and forge-std under `lib/forge-std/`.
## Default Network
The deploy script defaults to Sepolia ETH/USD:
```text
0x694AA1769357215DE4FAC081bf1f309aDC325306
```
Tell users to verify feed addresses against the official Chainlink docs before deploying. For L2 networks, add a Sequencer Uptime Feed check before production use.
## Commands
```sh
forge test
forge script script/PriceFeedConsumer.s.sol:DeployPriceFeedConsumer --rpc-url $SEPOLIA_RPC_URL --private-key $PRIVATE_KEY --broadcast
```
Example code is unaudited and should be reviewed before production use.
## Adapt This Template
The template is a complete, compiling reference. Two layers behave differently when you adapt it.
**Must be preserved exactly** — these are safety requirements, not style choices:
- The `AggregatorV3Interface` import path and the `latestRoundData()` destructuring, written exactly
as `(, int256 price,, uint256 updatedAt,) = priceFeed.latestRoundData();`. Every tuple element must
be declared in the destructuring. Assigning into a named return or other pre-existing variable
while declaring another element inline is a syntax error, not a style preference.
- The round-completion check (`updatedAt == 0`), the staleness check against a threshold, and the
positive-answer check. Every read path that returns or derives a price must pass through all three.
- Reading `decimals()` from the feed at runtime. Never hardcode a decimal count.
- Feed addresses and the dependency pins in `remappings.txt`. Copy these from the template or the
official docs rather than retyping them; a mistyped address literal fails EIP-55 checksum
validation and will not compile.
- On L2 networks (Arbitrum, Optimism, Base, Scroll, and similar) an L2 Sequencer Uptime Feed check
with a grace period, added before any price is trusted. The template targets Sepolia and does not
include one.
- Never using `answeredInRound` for freshness. The field is deprecated.
**Replace to fit the user's use case** — these are illustrative, not prescriptive:
- The `PriceFeedConsumer` contract name and file names.
- `getLatestPrice()` returning a raw `int256`, plus `getDecimals()` and `getPriceFeed()`. If the
application needs a derived value (a collateral ratio, a quote, a converted amount), expose that
and keep the raw read internal rather than shipping both surfaces.
- The immutable-vs-settable choice for the feed address.
- The deploy script's mock-vs-live branch and the test names.
**Tune, do not copy** — the template's values are placeholders:
- `stalenessThreshold` (template: `3600`). Look up the target feed's actual heartbeat, state it, and
derive the threshold from it as heartbeat plus a buffer. Heartbeats differ per feed and per chain,
and many are well under an hour. A threshold shorter than the heartbeat reverts during normal
operation; leaving the template's hour in place on a 20-minute feed silently widens the staleness
window.
- The mock's `DECIMALS` (`8`) and `INITIAL_ANSWER`. These match ETH/USD; other feeds differ.
When the derived value does arithmetic on the price, scale by the feed's runtime `decimals()`
instead of assuming 8. Widen runtime decimals before exponentiation—for example,
`10 ** uint256(feed.decimals())`; never exponentiate the `uint8` result directly or hardcode
the decimal count. Order operations so multiplication precedes division.
Derived-value tests must use exact integer fixtures instead of hand-encoding fractional 18-decimal
amounts. For example, with ETH at `2_000e8`, test a 150% collateral ratio using `3 ether` of
collateral and `4_000e18` of debt. Never append `e18` to digits that already encode 18 fractional
places; that scales the amount twice.
Errors carrying parameters (`StalePrice(uint256)`, `InvalidPrice(int256)`) must be asserted in tests
with `abi.encodeWithSelector(...)`. A bare `vm.expectRevert(Err.selector)` does not match a revert
payload that includes encoded arguments.
templates/starter-kit/remappings.txt
@chainlink/=lib/chainlink-evm/
forge-std/=lib/forge-std/src/
templates/starter-kit/script/PriceFeedConsumer.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import {Script} from "forge-std/Script.sol";
import {PriceFeedConsumer} from "../src/PriceFeedConsumer.sol";
import {MockV3Aggregator} from "../test/mocks/MockV3Aggregator.sol";
contract DeployPriceFeedConsumer is Script {
uint8 internal constant DECIMALS = 8;
int256 internal constant INITIAL_ANSWER = 2000e8;
uint256 internal constant STALENESS_THRESHOLD = 3600;
address internal constant SEPOLIA_ETH_USD_FEED = 0x694AA1769357215DE4FAC081bf1f309aDC325306;
function run() external returns (PriceFeedConsumer) {
vm.startBroadcast();
address priceFeed =
block.chainid == 11155111 ? SEPOLIA_ETH_USD_FEED : address(new MockV3Aggregator(DECIMALS, INITIAL_ANSWER));
PriceFeedConsumer consumer = new PriceFeedConsumer(priceFeed, STALENESS_THRESHOLD);
vm.stopBroadcast();
return consumer;
}
}
templates/starter-kit/src/PriceFeedConsumer.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
/**
* @title PriceFeedConsumer
* @notice Reads the latest answer from a Chainlink Data Feed with basic safety checks.
*/
contract PriceFeedConsumer {
AggregatorV3Interface internal immutable priceFeed;
uint256 public immutable stalenessThreshold;
error IncompleteRound();
error StalePrice(uint256 updatedAt);
error InvalidPrice(int256 price);
constructor(address _priceFeed, uint256 _stalenessThreshold) {
priceFeed = AggregatorV3Interface(_priceFeed);
stalenessThreshold = _stalenessThreshold;
}
/**
* @notice Returns the latest validated price.
* @return latest price using the feed's decimals.
*/
function getLatestPrice() public view returns (int256) {
(, int256 price,, uint256 updatedAt,) = priceFeed.latestRoundData();
if (updatedAt == 0) {
revert IncompleteRound();
}
if (block.timestamp - updatedAt > stalenessThreshold) {
revert StalePrice(updatedAt);
}
if (price <= 0) {
revert InvalidPrice(price);
}
return price;
}
/**
* @notice Returns the number of decimals used by the feed answer.
* @return feed decimals.
*/
function getDecimals() public view returns (uint8) {
return priceFeed.decimals();
}
/**
* @notice Returns the Price Feed address.
* @return Price Feed contract.
*/
function getPriceFeed() public view returns (AggregatorV3Interface) {
return priceFeed;
}
}
templates/starter-kit/test/mocks/MockV3Aggregator.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
contract MockV3Aggregator {
uint256 public constant version = 0;
uint8 public decimals;
int256 public latestAnswer;
uint256 public latestTimestamp;
uint256 public latestRound;
mapping(uint256 => int256) public getAnswer;
mapping(uint256 => uint256) public getTimestamp;
mapping(uint256 => uint256) private getStartedAt;
constructor(uint8 _decimals, int256 _initialAnswer) {
decimals = _decimals;
updateAnswer(_initialAnswer);
}
function updateAnswer(int256 _answer) public {
latestAnswer = _answer;
latestTimestamp = block.timestamp;
latestRound++;
getAnswer[latestRound] = _answer;
getTimestamp[latestRound] = block.timestamp;
getStartedAt[latestRound] = block.timestamp;
}
function updateRoundData(uint80 _roundId, int256 _answer, uint256 _timestamp, uint256 _startedAt) public {
latestRound = _roundId;
latestAnswer = _answer;
latestTimestamp = _timestamp;
getAnswer[latestRound] = _answer;
getTimestamp[latestRound] = _timestamp;
getStartedAt[latestRound] = _startedAt;
}
function getRoundData(uint80 _roundId)
external
view
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
{
return (_roundId, getAnswer[_roundId], getStartedAt[_roundId], getTimestamp[_roundId], _roundId);
}
function latestRoundData()
external
view
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
{
return (
uint80(latestRound),
getAnswer[latestRound],
getStartedAt[latestRound],
getTimestamp[latestRound],
uint80(latestRound)
);
}
function description() external pure returns (string memory) {
return "MockV3Aggregator";
}
}
templates/starter-kit/test/PriceFeedConsumer.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import {Test} from "forge-std/Test.sol";
import {PriceFeedConsumer} from "../src/PriceFeedConsumer.sol";
import {MockV3Aggregator} from "./mocks/MockV3Aggregator.sol";
contract PriceFeedConsumerTest is Test {
uint8 public constant DECIMALS = 8;
int256 public constant INITIAL_ANSWER = 2000e8;
uint256 public constant STALENESS_THRESHOLD = 3600;
PriceFeedConsumer public priceFeedConsumer;
MockV3Aggregator public mockV3Aggregator;
function setUp() public {
mockV3Aggregator = new MockV3Aggregator(DECIMALS, INITIAL_ANSWER);
priceFeedConsumer = new PriceFeedConsumer(address(mockV3Aggregator), STALENESS_THRESHOLD);
}
function testConsumerReturnsStartingValue() public view {
int256 price = priceFeedConsumer.getLatestPrice();
assertEq(price, INITIAL_ANSWER);
}
function testConsumerReturnsFeedDecimals() public view {
assertEq(priceFeedConsumer.getDecimals(), DECIMALS);
}
function testConsumerRevertsOnStalePrice() public {
vm.warp(block.timestamp + STALENESS_THRESHOLD + 1);
vm.expectRevert();
priceFeedConsumer.getLatestPrice();
}
function testConsumerRevertsOnInvalidPrice() public {
mockV3Aggregator.updateAnswer(0);
vm.expectRevert();
priceFeedConsumer.getLatestPrice();
}
}