# 24/7 Extended-Hours Data Feeds Source: https://docs.chain.link/data-feeds/24-7-extended-hours-data-feeds Some assets only trade during specific [market hours](/data-feeds/selecting-data-feeds#market-hours): precious metals and forex markets close on weekends and holidays, and some currencies trade mainly during local hours or are subject to capital controls. To support applications that need continuous price data anyway, Chainlink offers [custom feeds](/data-feeds/selecting-data-feeds#-custom-feeds) that publish 24/7 for certain assets in these categories. These feeds are not a single, uniform product. Each asset class uses a different methodology to produce a continuous price, and each has its own risk profile. Review the section below for the asset class you are integrating, alongside the [Custom Feeds](/data-feeds/selecting-data-feeds#-custom-feeds) section and [risk mitigation](/data-feeds/selecting-data-feeds#risk-mitigation) guidance appropriate for your use case. | Asset class | Description | | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | [Precious Metals](#precious-metals) | 24/7 blended feeds that report the spot price during market hours and a tokenized-stream-derived price when the market is closed. | | [Forex](#forex) | 24/7 crypto-derived rates for select currency pairs that lack a continuously observable institutional rate. | ## Precious Metals 24/7 blended precious metals price feeds publish a precious-metals-linked price both during and outside regular [precious metals market hours](/data-feeds/selecting-data-feeds#market-hours). They use a bespoke methodology rather than a continuously open physical precious metals spot market. ### How it works During [open precious metals market hours](/data-feeds/selecting-data-feeds#market-hours), the feed reports the underlying precious metals spot price (for example, XAU/USD for gold). When the precious metals market is *closed*, the feed starts from the last observed open-market price and adjusts it with a blended signal derived from tokenized precious metals price streams. For example, the current tGLD/USD implementation uses [XAUT/USD](/data-streams/crypto-streams?search=XAUT) and [PAXG/USD](/data-streams/crypto-streams?search=PAXG) streams for the closed-market signal. For each available tokenized precious metals stream, the adapter tracks the token's open-market premium or discount to the spot price with an EMA, uses that value to derive a spot-equivalent tokenized price, averages the valid derived prices, and smooths the resulting deviation from the last spot price with another EMA. The composite price applies configurable weighting and deviation controls before the closed-market adjustment is reflected in the answer. Other blended precious metals feeds may use different tokenized streams or methodology variations. ### Supported feeds The following table shows the available 24/7 blended precious metals feeds. Find these feeds on the [Price Feed Addresses](/data-feeds/price-feeds/addresses) page. ### Risks > **NOTE: 24/7 blended precious metals feed risks** > > These feeds use a custom methodology and are not equivalent to a continuously open physical precious metals spot > market. Outside regular precious metals market hours, the answer depends on tokenized precious metals markets, their > liquidity, their premium or discount to the underlying spot price, and the availability of the configured tokenized > streams. Users should monitor each feed against independent references, enforce freshness checks and value bounds > appropriate for their use case, and define pause or fallback behavior before using the feed to secure value, support > minting or redemption, trigger liquidations, or automate other protocol actions. If the tokenized inputs stop updating > or are unavailable, the adapter can continue from its most recent cached EMA state until valid inputs resume or the > precious metals market reopens. Contact [chainlink_data_feeds@smartcontract.com](mailto:chainlink_data_feeds@smartcontract.com) with questions. ## Forex Standard [Forex feeds](/data-feeds/selecting-data-feeds#market-hours) follow the global Forex market schedule (18:00 ET Sunday to 17:00 ET Friday). Some currencies, however, are thinly traded outside their local market hours, subject to capital controls, or otherwise lack a single institutional or interbank rate that is observable at all times. For a subset of these currency pairs, Chainlink offers 24/7 forex feeds that source a crypto-derived exchange rate instead of a traditional interbank or institutional rate. ### How it works A 24/7 crypto-derived forex rate is constructed by observing FX-to-stablecoin-to-FX arbitrage pathways: the implied exchange rate between two currencies, derived from the prices at which each currency trades against stablecoins and other crypto assets across global exchanges, 24 hours a day, 7 days a week. Because this rate is observed through crypto trading venues rather than through a country's official fixing mechanism or regulated banking channels, it is not constrained by capital controls and can be published continuously, including on weekends and local holidays. This also means a 24/7 crypto-derived rate measures a different market than an official, interbank, or institutional reference rate for the same currency pair, and the two can diverge, sometimes significantly. For example, [NCFX's analysis of its 24x7 USDARS rate](https://www.newchangefx.com/post/argentina-s-fifth-dollar-what-ncfx-s-24x7-usdars-rate-reveals) found the crypto-derived rate traded at a persistent premium of roughly 2.6% to 7.9% (averaging near 5%) above the institutional reference rate over a five-month period, with the premium widening or compressing in response to events such as central bank or IMF policy news. A 24/7 forex feed reflects the venue and methodology it is built on, not necessarily the rate you would get through a bank, a regulated exchange, or an official government fixing. ### Supported feeds The following table shows the available 24/7 forex feeds. ### Risks > **CAUTION: 24/7 forex feed risks: single data provider** > > 24/7 forex feeds currently rely on a single underlying data provider for their crypto-derived rate. Review the [Single > Source Data Providers](/data-feeds/selecting-data-feeds#single-source-data-providers) section before using one of > these feeds. In addition to the general single-source risks described there, be aware that a crypto-derived rate can > persistently and materially diverge from official, interbank, or institutional reference rates for the same currency > pair, and that this divergence is not static: it can widen or compress quickly around local political, regulatory, or > central bank events. Confirm that this rate construction matches your use case's definition of the currency pair, and > implement value bounds, freshness checks, monitoring against independent references, and pause or fallback behavior > before using one of these feeds to secure value, support minting or redemption, trigger liquidations, or automate > other protocol actions. Contact [chainlink_data_feeds@smartcontract.com](mailto:chainlink_data_feeds@smartcontract.com) with questions. --- # Data Feeds API Reference Source: https://docs.chain.link/data-feeds/api-reference When you use data feeds, retrieve the feeds through the `AggregatorV3Interface` and the proxy address. Optionally, you can call variables and functions in the `AccessControlledOffchainAggregator` contract to get information about the aggregator behind the proxy. ## AggregatorV3Interface Import this interface to your contract and use it to run functions in the proxy contract. Create the interface object by pointing to the proxy address. For example, on Sepolia you could create the interface object in the constructor of your contract using the following example: ```solidity /** * Network: Sepolia * Data Feed: ETH/USD * Address: 0x694AA1769357215DE4FAC081bf1f309aDC325306 */ constructor() { priceFeed = AggregatorV3Interface(0x694AA1769357215DE4FAC081bf1f309aDC325306); } ``` To see examples for how to use this interface, read the [Using Data Feeds](/data-feeds/price-feeds) guide. You can see the code for the [`AggregatorV3Interface` contract](https://github.com/smartcontractkit/chainlink/blob/contracts-v1.3.0/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol) on GitHub. ### Functions in AggregatorV3Interface | Name | Description | | ----------------------------------- | -------------------------------------------------------------------- | | [decimals](#decimals) | The number of decimals in the response. | | [description](#description) | The description of the aggregator that the proxy points to. | | [getRoundData](#getrounddata) | Get data from a specific round. | | [latestRoundData](#latestrounddata) | Get data from the latest round. | | [version](#version) | The version representing the type of aggregator the proxy points to. | #### decimals Get the number of decimals present in the response value. ```solidity function decimals() external view returns (uint8); ``` - `RETURN`: The number of decimals. #### description Get the description of the underlying aggregator that the proxy points to. ```solidity function description() external view returns (string memory); ``` - `RETURN`: The description of the underlying aggregator. #### getRoundData Get data about a specific round, using the `roundId`. ```solidity function getRoundData( uint80 _roundId ) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound); ``` **Parameters:** - `_roundId`: The round ID **Return values:** - `roundId`: The round ID - `answer`: The answer for this round - `startedAt`: Timestamp of when the round started - `updatedAt`: Timestamp of when the round was updated - `answeredInRound`: Deprecated - Previously used when answers could take multiple rounds to be computed #### latestRoundData Get the data from the latest round. ```solidity function latestRoundData() external view returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) ``` **Return values:** - `roundId`: The round ID. - `answer`: The data that this specific feed provides. Depending on the feed you selected, this answer provides asset prices, reserves, and other types of data. - `startedAt`: Timestamp of when the round started. - `updatedAt`: Timestamp of when the round was updated. - `answeredInRound`: Deprecated - Previously used when answers could take multiple rounds to be computed #### version The version representing the type of aggregator the proxy points to. ```solidity function version() external view returns (uint256) ``` - `RETURN`: The version number. ## AccessControlledOffchainAggregator This is the contract for the aggregator. You can call functions on the aggregator directly, but it is a best practice to use the [AggregatorV3Interface](#aggregatorv3interface) to run functions on the proxy instead so that changes to the aggregator do not affect your application. Read the aggregator contract only if you need functions that are not available in the proxy. The aggregator contract has several variables and functions that might be useful for your application. Although aggregator contracts are similar for each data feed, some aggregators have different variables. Use the `typeAndVersion()` function on the aggregator to identify what type of aggregator it is and what version it is running. Always check the contract source code and configuration to understand how specific data feeds operate. For example, the [aggregator contract for BTC/USD on Arbitrum](https://arbiscan.io/address/0x942d00008D658dbB40745BBEc89A93c253f9B882#code) is different from the aggregators on other networks. For examples of the contracts that are typically used in aggregator deployments, see the [libocr repository](https://github.com/smartcontractkit/libocr/blob/master/contract/) on GitHub. ### Variables and functions in AccessControlledOffchainAggregator This contract imports `OffchainAggregator` and `SimpleReadAccessController`, which also include their own imports. The variables and functions lists include the publicly accessible items from these imported contracts. A simple way to read the variables or functions is to get the ABI from a blockchain explorer and point the ABI to the aggregator address. To do this in Remix, follow the [Using the ABI with AtAddress](https://remix-ide.readthedocs.io/en/latest/run.html#using-the-abi-with-ataddress) guide in the Remix documentation. As an example, you can find the ABI for the BTC/USD aggregator by viewing the [contract code in Etherscan](https://etherscan.io/address/0xAe74faA92cB67A95ebCAB07358bC222e33A34dA7#code). **Variables:** | Name | Description | | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | LINK | The address for the LINK token contract on a specific network. | | billingAccessController | The address for the billingAccessController, which limits access to the [billing configuration](https://github.com/smartcontractkit/libocr/blob/master/contract/OffchainAggregatorBilling.sol) for the aggregator. | | checkEnabled | A boolean that indicates if access is limited to addresses on the internal access list. | | maxAnswer | This value is no longer used on most Data Feeds. Evaluate if your use case for Data Feeds requires a custom circuit breaker and implement it to meet the needs of your application. See the [Risk Mitigation](/data-feeds/selecting-data-feeds#risk-mitigation) page for more information. | | minAnswer | This value is no longer used on most Data Feeds. Evaluate if your use case for Data Feeds requires a custom circuit breaker and implement it to meet the needs of your application. See the [Risk Mitigation](/data-feeds/selecting-data-feeds#risk-mitigation) page for more information. | | owner | The address that owns this aggregator contract. This controls which address can execute specific functions. | **Functions:** | Name | Description | | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [decimals](#decimals-1) | Return the number of digits of precision for the stored answer. Answers are stored in fixed-point format. | | [description](#description-1) | Return a description for this data feed. This is different depending on which feed you select. | | [getAnswer](#getanswer) | Deprecated - Do not use this function. | | [getBilling](#getbilling) | Retrieve the current billing configuration. | | [getRoundData](#getrounddata-1) | Get the full information for a specific aggregator round including the answer and update timestamps. Use this to get the full historical data for a round. | | [getTimestamp](#gettimestamp) | Deprecated - Do not use this function. | | [hasAccess](#hasaccess) | Check if an address has internal access. | | [latestAnswer](#latestanswer) | Return the latest answer for this data feed. No timestamp is included to check data freshness. | | [latestConfigDetails](#latestconfigdetails) | Return information about the current offchain reporting protocol configuration. | | [latestRound](#latestround) | Deprecated - Do not use this function. | | [latestRoundData](#latestrounddata-1) | Get the full information for the most recent round including the answer and update timestamps. | | [latestTimestamp](#latesttimestamp) | Deprecated - Do not use this function. | | [latestTransmissionDetails](#latesttransmissiondetails) | Get information about the most recent answer. | | [linkAvailableForPayment](#linkavailableforpayment) | Get the amount of LINK on this contract that is available to make payments to oracles. This value can be negative if there are outstanding payment obligations. | | [oracleObservationCount](#oracleobservationcount) | Returns the number of observations that oracle is due to be reimbursed for. | | [owedPayment](#owedpayment) | Returns how much LINK an oracle is owed for its observations. | | [requesterAccessController](#requesteraccesscontroller) | Returns the address for the access controller contract. | | [transmitters](#transmitters) | The oracle addresses that can report answers to this aggregator. | | [typeAndVersion](#typeandversion) | Returns the aggregator type and version. Many aggregators are `AccessControlledOffchainAggregator 3.0.0`, but there are other variants in production. The version is for the type of aggregator, and different from the contract `version`. | | [validatorConfig](#validatorconfig) | Returns the address and the gas limit for the validator contract. | | [version](#version-1) | Returns the contract version. This is different from the `typeAndVersion` for the aggregator. | #### decimals Return the number of digits of precision for the stored answer. Answers are stored in fixed-point format. ```solidity function decimals() external view returns (uint8 decimalPlaces); ``` #### description Return a description for this data feed. Usually this is an asset pair for a price feed. ```solidity function description() public view override checkAccess returns (string memory) { return super.description(); } ``` #### getAnswer > **CAUTION: This function is deprecated. Do not use this function.** #### getBilling Retrieve the current billing configuration. ```solidity function getBilling() external view returns ( uint32 maximumGasPrice, uint32 reasonableGasPrice, uint32 microLinkPerEth, uint32 linkGweiPerObservation, uint32 linkGweiPerTransmission ) { Billing memory billing = s_billing; return ( billing.maximumGasPrice, billing.reasonableGasPrice, billing.microLinkPerEth, billing.linkGweiPerObservation, billing.linkGweiPerTransmission ); } ``` #### getRoundData Get the full information for a specific aggregator round including the answer and update timestamps. Use this to get the full historical data for a round. ```solidity function getRoundData( uint80 _roundId ) public view override checkAccess returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) { return super.getRoundData(_roundId); } ``` #### getTimestamp > **CAUTION: This function is deprecated. Do not use this function.** #### hasAccess Check if an address has internal access. ```solidity function hasAccess(address _user, bytes memory _calldata) public view virtual override returns (bool) { return super.hasAccess(_user, _calldata) || _user == tx.origin; } ``` #### latestAnswer Return the latest answer for this data feed. No timestamp is included to check data freshness. Returns `0` if no writes have occurred. `latestRoundData` includes the latest answer and timestamps for checking data freshness. #### latestConfigDetails Return information about the current offchain reporting protocol configuration. ```solidity function latestConfigDetails() external view returns (uint32 configCount, uint32 blockNumber, bytes16 configDigest) { return (s_configCount, s_latestConfigBlockNumber, s_hotVars.latestConfigDigest); } ``` #### latestRound > **CAUTION: This function is deprecated. Do not use this function.** #### latestRoundData Get the full information for the most recent round including the answer and update timestamps. ```solidity function latestRoundData() public view override checkAccess returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) { return super.latestRoundData(); } ``` #### latestTimestamp > **CAUTION: This function is deprecated. Do not use this function.** #### latestTransmissionDetails Get information about the most recent answer. ```solidity function latestTransmissionDetails() external view returns (bytes16 configDigest, uint32 epoch, uint8 round, int192 latestAnswer, uint64 latestTimestamp) { require(msg.sender == tx.origin, "Only callable by EOA"); return ( s_hotVars.latestConfigDigest, uint32(s_hotVars.latestEpochAndRound >> 8), uint8(s_hotVars.latestEpochAndRound), s_transmissions[s_hotVars.latestAggregatorRoundId].answer, s_transmissions[s_hotVars.latestAggregatorRoundId].timestamp ); } ``` #### linkAvailableForPayment Get the amount of LINK on this contract that is available to make payments to oracles. This value can be negative if there are outstanding payment obligations. ```solidity function linkAvailableForPayment() external view returns (int256 availableBalance) { // there are at most one billion LINK, so this cast is safe int256 balance = int256(LINK.balanceOf(address(this))); // according to the argument in the definition of totalLINKDue, // totalLINKDue is never greater than 2**172, so this cast is safe int256 due = int256(totalLINKDue()); // safe from overflow according to above sizes return int256(balance) - int256(due); } ``` #### oracleObservationCount Returns the number of observations that oracle is due to be reimbursed for. ```solidity function oracleObservationCount(address _signerOrTransmitter) external view returns (uint16) { Oracle memory oracle = s_oracles[_signerOrTransmitter]; if (oracle.role == Role.Unset) { return 0; } return s_oracleObservationsCounts[oracle.index] - 1; } ``` #### owedPayment Returns how much LINK an oracle is owed for its observations. ```solidity function owedPayment(address _transmitter) public view returns (uint256) { Oracle memory oracle = s_oracles[_transmitter]; if (oracle.role == Role.Unset) { return 0; } Billing memory billing = s_billing; uint256 linkWeiAmount = uint256(s_oracleObservationsCounts[oracle.index] - 1) * uint256(billing.linkGweiPerObservation) * (1 gwei); linkWeiAmount += s_gasReimbursementsLinkWei[oracle.index] - 1; return linkWeiAmount; } ``` #### requesterAccessController Returns the address for the access controller contract. ```solidity function requesterAccessController() external view returns (AccessControllerInterface) { return s_requesterAccessController; } ``` #### transmitters The oracle addresses that can report answers to this aggregator. ```solidity function transmitters() external view returns (address[] memory) { return s_transmitters; } ``` #### typeAndVersion Returns the aggregator type and version. Many aggregators are `AccessControlledOffchainAggregator 2.0.0`, but there are other variants in production. The version is for the type of aggregator, and different from the contract `version`. ```solidity function typeAndVersion() external pure virtual override returns (string memory) { return "AccessControlledOffchainAggregator 2.0.0"; } ``` #### validatorConfig Returns the address and the gas limit for the validator contract. ```solidity function validatorConfig() external view returns (AggregatorValidatorInterface validator, uint32 gasLimit) { ValidatorConfig memory vc = s_validatorConfig; return (vc.validator, vc.gasLimit); } ``` #### version Returns the contract version. This is different from the `typeAndVersion` for the aggregator. ```solidity function version() external view returns (uint256); ``` --- # Using Data Feeds on Aptos Source: https://docs.chain.link/data-feeds/aptos [Aptos](https://aptos.dev/) is a Layer 1 blockchain that uses the [Move](https://move-language.github.io/move/) programming language for smart contracts. Chainlink Data Feeds on Aptos provides data through a single price feed contract that handles multiple data feeds. You interact with this contract by passing the specific feed ID(s) for the data you need. This contrasts with Chainlink's integration on other blockchains, where each price feed has a separate contract address. ## Getting Started (CLI) This guide explains how to use Chainlink Data Feeds with your Move smart contracts on Aptos testnet using the `Benchmark` structure provided by the data feeds contract. You will use the [Aptos CLI](https://aptos.dev/en/build/cli) to compile, publish, and interact with your contract. ### Requirements Make sure you have the [Aptos CLI](https://aptos.dev/en/build/cli) installed. You can run aptos help in your terminal to verify if the CLI is correctly installed. ### Integrate Chainlink Data Feeds on Aptos Create a new directory for your project and navigate to it in your terminal: ```bash mkdir aptos-data-feeds && cd aptos-data-feeds ``` #### Set up your Aptos testnet account 1. Run the following command in your terminal to create a new account on testnet: ```bash aptos init --network=testnet --assume-yes ``` 2. You are prompted to enter a private key: ```bash Configuring for profile default Configuring for network Testnet Enter your private key as a hex literal (0x...) [Current: Redacted | No input: Generate new key (or keep one if present)] ``` Press `Enter` to generate a new key pair with the [`ed25519` key scheme](https://aptos.dev/en/network/blockchain/accounts#authentication-key). Expect an output similar to the following: ```bash No key given, generating key... Account 0x35107a273065fc0f428b2db719145682f6b5bf16a32b071ccd649fcd8b1a44e9 doesn't exist, creating it and funding it with 100000000 Octas Account 0x35107a273065fc0f428b2db719145682f6b5bf16a32b071ccd649fcd8b1a44e9 funded successfully --- Aptos CLI is now set up for account 0x35107a273065fc0f428b2db719145682f6b5bf16a32b071ccd649fcd8b1a44e9 as profile default! See the account here: https://explorer.aptoslabs.com/account/0x35107a273065fc0f428b2db719145682f6b5bf16a32b071ccd649fcd8b1a44e9?network=testnet Run `aptos --help` for more information about commands { "Result": "Success" } ``` You now have a funded testnet account on Aptos. #### Set up your project 1. Initialize a Move package in your `aptos-data-feeds` directory: ```bash aptos move init --name aptos-data-feeds ``` Expect the following output: ```bash { "Result": "Success" } ``` You should now have a Move project with the following structure: ```plaintext Move.toml ├── /.aptos ├── /scripts ├── /sources └── /tests ``` 2. Update your `Move.toml` file to include the required dependencies and addresses: ```toml [package] name = "my-app" version = "1.0.0" authors = [] [addresses] sender = "" owner = "" data_feeds = "0xf1099f135ddddad1c065203431be328a408b0ca452ada70374ce26bd2b32fdd3" platform = "0x516e771e1b4a903afe74c27d057c65849ecc1383782f6642d7ff21425f4f9c99" move_stdlib = "0x1" aptos_std = "0x1" [dev-addresses] [dependencies] AptosFramework = { git = "https://github.com/aptos-labs/aptos-core.git", subdir = "aptos-move/framework/aptos-framework", rev = "main" } MoveStdlib = { git = "https://github.com/aptos-labs/aptos-core.git", subdir = "aptos-move/framework/move-stdlib", rev = "main" } ChainlinkDataFeeds = { local = "./ChainlinkDataFeeds" } ``` Where: - `` is your Aptos testnet account address. You can find your address in the `~/.aptos/config.yaml` file. Alternatively, run the following command in your terminal to retrieve it: ```bash aptos config show-profiles --profile=default ``` - The `data_feeds` address is the `ChainlinkDataFeeds` package address on Aptos testnet ([`0xf1099f135ddddad1c065203431be328a408b0ca452ada70374ce26bd2b32fdd3`](https://explorer.aptoslabs.com/object/0xf1099f135ddddad1c065203431be328a408b0ca452ada70374ce26bd2b32fdd3/modules/packages/ChainlinkDataFeeds?network=testnet)). - The `platform` address is the `ChainlinkPlatform` package address on Aptos testnet ([`0x516e771e1b4a903afe74c27d057c65849ecc1383782f6642d7ff21425f4f9c99`](https://explorer.aptoslabs.com/object/0x516e771e1b4a903afe74c27d057c65849ecc1383782f6642d7ff21425f4f9c99/modules/packages/ChainlinkPlatform?network=testnet)). **Note**: You can find the mainnet addresses in the [Feeds Addresses](/data-feeds/price-feeds/addresses?network=aptos) page. 3. Run the following command to download the compiled bytecode for the `ChainlinkPlatform` and `ChainlinkDataFeeds` packages: ```bash aptos move download --account 0x516e771e1b4a903afe74c27d057c65849ecc1383782f6642d7ff21425f4f9c99 --package ChainlinkPlatform && \ aptos move download --account 0xccad6853cabea164842907df3de4f89bb34be5bf249bbf16939f9c90db1bf63b --package ChainlinkDataFeeds ``` Expect an output similar to the following: ```bash Saved package with 2 module(s) to `~/aptos-data-feeds/ChainlinkPlatform` { "Result": "Download succeeded" } Saved package with 2 module(s) to `~/aptos-data-feeds/ChainlinkDataFeeds` { "Result": "Download succeeded" } ``` 4. Open the `ChainlinkDataFeeds` package configuration file (`ChainlinkDataFeeds/Move.toml`) and update the `ChainlinkPlatform` dependency to point to your local path: ```toml ChainlinkPlatform = { local = "../ChainlinkPlatform" } ``` #### Write the Move contract to interact with Chainlink Data Feeds In this step, you will develop a Move module that interacts with Chainlink Data Feeds to fetch the latest price and timestamp for a specified feed ID. This module retrieves benchmark data for the given feed ID, extracts the price and timestamp, and stores this information in the invoking account's global storage. You can then retrieve this data using a view function. 1. Create a new Move module in the `sources` directory of your project: ```bash touch sources/MyOracleContract.move ``` 2. Insert the following code example and save your `MyOracleContract.move` file: ```rust module sender::MyOracleContractTest { use std::vector; use std::signer; use data_feeds::router::get_benchmarks; use data_feeds::registry::{Benchmark, get_benchmark_value, get_benchmark_timestamp}; use move_stdlib::option::{Option, some, none}; struct PriceData has copy, key, store { /// The price value with 18 decimal places of precision price: u256, /// Unix timestamp in seconds timestamp: u256, } // Function to fetch and store the price data for a given feed ID public entry fun fetch_price(account: &signer, feed_id: vector) acquires PriceData { let feed_ids = vector[feed_id]; // Use the passed feed_id let billing_data = vector[]; let benchmarks: vector = get_benchmarks(account, feed_ids, billing_data); let benchmark = vector::pop_back(&mut benchmarks); let price: u256 = get_benchmark_value(&benchmark); let timestamp: u256 = get_benchmark_timestamp(&benchmark); // Check if PriceData exists and update it if (exists(signer::address_of(account))) { let data = borrow_global_mut(signer::address_of(account)); data.price = price; data.timestamp = timestamp; } else { // If PriceData does not exist, create a new one move_to(account, PriceData { price, timestamp }); } } // View function to get the stored price data #[view] public fun get_price_data(account_address: address): Option acquires PriceData { if (exists(account_address)) { let data = borrow_global(account_address); some(*data) } else { none() } } } ``` #### Compile and publish the contract 1. Compile and publish the contract to the Aptos testnet: ```bash aptos move publish --skip-fetch-latest-git-deps ``` - You are prompted to confirm the transaction details. Type `yes` and press `Enter` to proceed. ```bash Compiling, may take a little while to download git dependencies... INCLUDING DEPENDENCY AptosFramework INCLUDING DEPENDENCY AptosStdlib INCLUDING DEPENDENCY ChainlinkDataFeeds INCLUDING DEPENDENCY ChainlinkKeystone INCLUDING DEPENDENCY MoveStdlib BUILDING my-app package size 2133 bytes Do you want to submit a transaction for a range of [176700 - 265000] Octas at a gas unit price of 100 Octas? [yes/no] > ``` - Expect an output similar to the following: ```bash Transaction submitted: https://explorer.aptoslabs.com/txn/0x22a65eedb37ad7e41e195409c06e23b154c71bba11c73e6f67df6ba41e6768a4?network=testnet { "Result": { "transaction_hash": "0x22a65eedb37ad7e41e195409c06e23b154c71bba11c73e6f67df6ba41e6768a4", "gas_used": 1767, "gas_unit_price": 100, "sender": "4006c4623e114a6169b409cc0ad91b0780e62667633ce2e069b31125cc6a5d37", "sequence_number": 0, "success": true, "timestamp_us": 1731707700843776, "version": 6238520375, "vm_status": "Executed successfully" } } ``` **Notes**: - Your `default` profile within the `~/.aptos/config.yaml` file is used by default. You can specify a different profile using the `--profile` flag. - Ensure you have enough testnet APT tokens in your account to cover the deployment fees. You can fund your account with testnet APT tokens using the following command, where the amount used is in Octas (1 APT = 100,000,000 Octas): ```bash aptos account fund-with-faucet --account --amount 100000000 ``` #### Interact with the deployed contract In this step, you will interact with your deployed contract to fetch the BTC/USD price and timestamp. The BTC/USD feed ID on Aptos testnet is: 0x01a0b4d920000332000000000000000000000000000000000000000000000000. You can find the feed ID for other assets in the [Feed Addresses page](/data-feeds/price-feeds/addresses?network=aptos\&page=1). 1. Fetch benchmark data for the BTC/USD feed, extract the price and timestamp, and store this information in your account's global storage. - Run the following command, replacing `` with your actual account address: ```bash aptos move run \ --function-id '::MyOracleContractTest::fetch_price' \ --args hex:0x01a0b4d920000332000000000000000000000000000000000000000000000000 ``` **Note**: `` is your Aptos testnet account address. You can find your address in the `~/.aptos/config.yaml` file. Alternatively, run the following command in your terminal to retrieve it: ```bash aptos config show-profiles --profile=default ``` - You are prompted to confirm the transaction details. Type `yes` and press `Enter` to proceed: ```bash Do you want to submit a transaction for a range of [47200 - 70800] Octas at a gas unit price of 100 Octas? [yes/no] > ``` - Expect an output similar to the following: ```bash Transaction submitted: https://explorer.aptoslabs.com/txn/0x7180e890ececbcab4f052f84ecae21d37f33a31d0e475f566f92fc6df157c725?network=testnet { "Result": { "transaction_hash": "0x7180e890ececbcab4f052f84ecae21d37f33a31d0e475f566f92fc6df157c725", "gas_used": 472, "gas_unit_price": 100, "sender": "4006c4623e114a6169b409cc0ad91b0780e62667633ce2e069b31125cc6a5d37", "sequence_number": 1, "success": true, "timestamp_us": 1731707764586551, "version": 6238524110, "vm_status": "Executed successfully" } } ``` 2. Retrieve this data using the view function: - Run the following command, replacing `` with your actual account address: ```bash aptos move view \ --function-id '::MyOracleContractTest::get_price_data' \ --args address: ``` - Expect an output similar to the following: ```bash { "Result": [ { "vec": [ { "price": "70376345325832900000000", "timestamp": "1730827175" } ] } ] } ``` Where: - `price` is the latest BTC/USD price with 18 decimal places. - `timestamp` is the Unix timestamp in seconds when the price data was last updated onchain by the Chainlink oracle network. --- # Flags Contract Registry Source: https://docs.chain.link/data-feeds/contract-registry The Chainlink Flags Contract Registry provides a reliable, onchain source of truth for all active Chainlink price feed proxy contracts across supported networks. It serves as a central reference point for identifying which data feeds are **owned and operated by Chainlink**, verifying the authenticity of the feeds your applications rely on. A proxy address that returns `true` when checked against this registry indicates two things: 1. The data feed is officially owned and operated by Chainlink 2. The feed is currently active Inactive feeds are removed from the registry to ensure accuracy. The list is actively maintained as new feeds are deployed, making it a trusted resource for keeping up with the evolving state of Chainlink's oracle infrastructure. ## Querying Flags Onchain To check if a proxy address is flagged (owned by Chainlink and active) in your Solidity code, you can call the `getFlag` function on the `Flags` contract deployed on the corresponding network: ```solidity interface IFlags { function getFlag(address) external view returns (bool); } contract Example { IFlags public flags; // Initialize with the correct Flags contract address for your network constructor(address flagsContractAddress) { flags = IFlags(flagsContractAddress); } function isOfficialChainlinkFeed(address proxy) public view returns (bool) { return flags.getFlag(proxy); } } ``` ## Flags Contract Addresses > **NOTE: This list isn't exhaustive** > > Not every chain where Chainlink Data Feeds are live has a Flags contract. If your chain is missing from the table > below, reach out to [chainlink_data_feeds@smartcontract.com](mailto:chainlink_data_feeds@smartcontract.com) to confirm > feed status. Below is a list of all available Flags contracts on the supported networks: | | Chain | ChainFlag Contract Address | | - | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | | Arbitrum | [0x20551B03c092D998b1410c47BD54004D7C3106D0](https://arbiscan.io/address/0x20551B03c092D998b1410c47BD54004D7C3106D0) | | | Avalanche | [0x71c5CC2aEB9Fa812CA360E9bAC7108FC23312cdd](https://snowtrace.io/address/0x71c5CC2aEB9Fa812CA360E9bAC7108FC23312cdd) | | | Base | [0x71c5CC2aEB9Fa812CA360E9bAC7108FC23312cdd](https://basescan.org/address/0x71c5CC2aEB9Fa812CA360E9bAC7108FC23312cdd) | | | Bob | [0xaB93491064aEE774BE4b8a1cFFe4421F5B124F4e](https://explorer.gobob.xyz/address/0xaB93491064aEE774BE4b8a1cFFe4421F5B124F4e) | | | BNB Chain (BSC) | [0x141f4278A5D71070Dc09CA276b72809b80F20eF0](https://bscscan.com/address/0x141f4278A5D71070Dc09CA276b72809b80F20eF0) | | | Celo | [0xaB93491064aEE774BE4b8a1cFFe4421F5B124F4e](https://celo.blockscout.com/address/0xaB93491064aEE774BE4b8a1cFFe4421F5B124F4e) | | | Ethereum | [0xaB93491064aEE774BE4b8a1cFFe4421F5B124F4e](https://etherscan.io/address/0xaB93491064aEE774BE4b8a1cFFe4421F5B124F4e) | | | Gnosis Chain (xDai) | [0xaB93491064aEE774BE4b8a1cFFe4421F5B124F4e](https://gnosisscan.io/address/0xaB93491064aEE774BE4b8a1cFFe4421F5B124F4e) | | | Ink | [0xaB93491064aEE774BE4b8a1cFFe4421F5B124F4e](https://explorer.inkonchain.com/address/0xaB93491064aEE774BE4b8a1cFFe4421F5B124F4e) | | | Linea | [0xaB93491064aEE774BE4b8a1cFFe4421F5B124F4e](https://lineascan.build/address/0xaB93491064aEE774BE4b8a1cFFe4421F5B124F4e) | | | Mantle | [0x141f4278A5D71070Dc09CA276b72809b80F20eF0](https://mantlescan.xyz/address/0x141f4278A5D71070Dc09CA276b72809b80F20eF0) | | | Monad | [0x2C4936af7C8867e62DAF9874519239dBE6b6DfFD](https://monadvision.com/address/0x2C4936af7C8867e62DAF9874519239dBE6b6DfFD) | | | Optimism | [0x71c5CC2aEB9Fa812CA360E9bAC7108FC23312cdd](https://optimistic.etherscan.io/address/0x71c5CC2aEB9Fa812CA360E9bAC7108FC23312cdd) | | | Polygon | [0xaB93491064aEE774BE4b8a1cFFe4421F5B124F4e](https://polygonscan.com/address/0xaB93491064aEE774BE4b8a1cFFe4421F5B124F4e) | | | Robinhood | [0xbb601D8e5e568e6464D6D34feE489AA61b8d035A](https://robinhoodchain.blockscout.com/address/0xbb601D8e5e568e6464D6D34feE489AA61b8d035A) | | | Scroll | [0x141f4278A5D71070Dc09CA276b72809b80F20eF0](https://scrollscan.com/address/0x141f4278A5D71070Dc09CA276b72809b80F20eF0) | | | Soneium | [0x3DE960FE090BFec72F585347fa0a27CF96a83b36](https://soneium.blockscout.com/address/0x3DE960FE090BFec72F585347fa0a27CF96a83b36) | | | Sonic | [0x141f4278A5D71070Dc09CA276b72809b80F20eF0](https://sonicscan.org/address/0x141f4278A5D71070Dc09CA276b72809b80F20eF0) | | | Tempo | [0xae22AF6B02A4E7CCc535D1670442B29a3ef69a88](https://explore.tempo.xyz/address/0xae22AF6B02A4E7CCc535D1670442B29a3ef69a88) | | | UniChain | [0xaB93491064aEE774BE4b8a1cFFe4421F5B124F4e](https://uniscan.xyz/address/0xaB93491064aEE774BE4b8a1cFFe4421F5B124F4e) | | | zkSync | [0xC370405879C1ab0470604679E3275a02bCb89C91](https://explorer.zksync.io/address/0xC370405879C1ab0470604679E3275a02bCb89C91) | --- # Data Sources Source: https://docs.chain.link/data-feeds/data-sources Chainlink Data Feeds and Data Streams source data across multiple independent aggregators, vendors, and authoritative entities. The table below summarizes sourcing models for each asset type. For more information about evaluating data quality, assessing liquidity and distribution risks, understanding single-source providers, and implementing risk mitigation strategies, see [Evaluating Data Sources and Risks](/data-feeds/selecting-data-feeds#evaluating-data-sources-and-risks). | Asset Type | Data Sourcing Info | | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | Crypto Price | 3+ crypto price data aggregators and/or market data vendors using CEX and/or DEX data | | Crypto State Price | 3+ crypto price data aggregators and/or market data vendors using DEX pool state data | | Forex | 3+ market data vendors | | Precious Metals | 3+ market data vendors | | US Oil | 3+ market data vendors | | US Equities | 3+ market data vendors | | UK & Euro ETFs | 2+ market data vendors using 15 minute delayed data | | SmartData PoR (offchain) | Sourced directly from custodian, fund administrator, auditor, security auditor, asset manager, regulated appraiser of assets, or regulator approved source | | SmartData PoR (cross chain) | On-chain data | | SmartData NAV (offchain) | Sourced directly from Fund Administrator or Asset Manager of the fund | | Exchange Rate Feeds | On-chain data | | Crypto MarketCap | 3+ crypto price data aggregators and/or market data vendors | | Composite Index Feeds | DON calculated index values from 3+ crypto price data aggregators and/or market data vendors | | Central Bank Rates Feeds | Direct from Central Bank's API | | Crypto LWBA | 3+ crypto price data aggregators and/or market data vendors using CEX orderbook data to produce Liquidity Weight Bid/Ask and Mid values | | Crypto State Price | 3+ crypto price data aggregators and/or market data vendors using DEX pool state data | | Data Link Long Tail Crypto | Single source from 1 crypto price data aggregator | | Forex | 3+ market data vendors | | Precious Metals | 3+ market data vendors | | US Equities | 3+ market data vendors | > **NOTE: Need more details?** > > For additional information about data sourcing, please > [contact us](https://chain.link/contact?ref_id=pricefeeds). --- # Deprecation of Chainlink Data Feeds Source: https://docs.chain.link/data-feeds/deprecating-feeds ## Deprecated Feeds This page contains dynamically generated or component-heavy content. For the full and most up-to-date information, see: https://docs.chain.link/data-feeds/deprecating-feeds --- # Developer Responsibilities: Market Integrity and Application Code Risks Source: https://docs.chain.link/data-feeds/developer-responsibilities Chainlink Data Feeds provide access to highly secure, reliable, and decentralized real-world data published onchain. The assets priced by Chainlink Data Feeds are subject to market conditions beyond the ability of Chainlink node operators to control, as such developers are responsible for ensuring that the operation and performance of Chainlink Data Feeds match expectations. When integrating Chainlink Data Feeds, developers must understand that the performance of feeds is subject to risks associated with both market integrity and application code. - **Market Integrity Risks** are those associated with external market conditions impacting price behavior and data quality in unanticipated ways. Developers are solely responsible for monitoring and mitigating any potential market integrity risks. - **Application Code Risks** are those associated with the quality, reliability, and dependencies of the code on which an application operates. Developers are solely responsible for monitoring and mitigating any potential application code risks. Please [refer to this guide for additional information](https://chain.link/education-hub/market-manipulation-vs-oracle-exploits) about market integrity risks and how developers can protect their applications. ## Developer Responsibilities Developers are responsible for maintaining the security and user experience of their applications. They must also securely manage all interactions between their applications and third-party services. In particular, developers implementing Chainlink Data Feeds in their code and applications are responsible for their application's market integrity and code risks that may cause unanticipated pricing data behavior. These are described below in more detail. ### Market Integrity Risks Market conditions can impact the pricing behavior of assets in ways beyond the ability of Chainlink node operators to predict or control. Market integrity risk factors can include but are not limited to, [market manipulation](https://chain.link/education-hub/market-manipulation-vs-oracle-exploits) such as Spoofing, Ramping, Bear Raids, Cross-Market Manipulation, Washtrading, and Frontrunning. All assets are susceptible to [market risk](/data-feeds/selecting-data-feeds#data-feed-categories), but in particular, assets with high market risk, such as those with low liquidity, are the most vulnerable to market manipulation. Developers are solely responsible for accounting for such risk factors when integrating Chainlink Data Feeds into their applications. Developers should understand the market risks around the assets they intend their application to support before integrating associated Chainlink Data Feeds and inform their end users about applicable market risks. Developers should reference the following additional information when implementing Chainlink Data Feeds: - [Data Feed Categories](/data-feeds/selecting-data-feeds#data-feed-categories) to evaluate market integrity risks associated with specific Chainlink Data Feeds Developers intend to integrate. - [Evaluating Data Source Risks](/data-feeds/selecting-data-feeds#evaluating-data-sources-and-risks) to evaluate risk mitigation techniques associated with Chainlink Data Feeds broadly. ### Application Code Risks Developers implementing Chainlink Data Feeds are solely responsible for instituting the requisite [risk mitigation processes](/data-feeds/selecting-data-feeds#risk-mitigation) including, but not limited to, data quality checks, circuit breakers, and appropriate contingency logic for their use case. - **Code quality and reliability:** Developers must execute code using Chainlink Data Feeds only if the code meets the quality and reliability requirements for their use case and application. - **Code and application audits:** Developers are responsible for auditing their code and applications before deploying to production. Developers must determine the quality of any audits and ensure that they meet the requirements for their application. - **Code dependencies and imports:** Developers are responsible for ensuring the quality, reliability, and security of any dependencies or imported packages that they use with Chainlink Data Feeds, and review and audit these dependencies and packages. ![Image](/images/developer-responsibilities.webp) --- # Using ENS with Data Feeds Source: https://docs.chain.link/data-feeds/ens ## Lookup ## Manual Lookup ## Naming structure Chainlink data feeds fall under the `data.eth` naming suffix. To obtain a specific feed address, prefix this with the assets in the feed, separated by a dash (-). | Pair | ENS Domain Name | | :-------- | :----------------- | | ETH / USD | `eth-usd.data.eth` | | BTC / USD | `btc-usd.data.eth` | | ... | `...` | ### Subdomains By default, the base name structure (`eth-usd.data.eth`) returns the proxy address for that feed. However, subdomains enable callers to retrieve other associated contract addresses, as shown in the following table. | Contract Addresses | Subdomain Prefix | Example | | :-------------------- | :--------------- | :---------------------------- | | Proxy | `proxy` | `proxy.eth-usd.data.eth` | | Underlying aggregator | `aggregator` | `aggregator.eth-usd.data.eth` | | Proposed aggregator | `proposed` | `proposed.eth-usd.data.eth` | ## Architecture ### Resolver For each network, there is a single Chainlink resolver, which does not change. Its address can be obtained using the `data.eth` domain. This resolver manages the subdomains associated with `data.eth`. | Network | Resolver Address | | :--------------- | :----------------------------------------------------------------------------------------------------------------------- | | Ethereum Mainnet | [0x122eb74f9d0F1a5ed587F43D120C1c2BbDb9360B](https://app.ens.domains/address/0x122eb74f9d0F1a5ed587F43D120C1c2BbDb9360B) | ### Listening for address changes When a new aggregator is deployed for a specific feed, it is first proposed, and when accepted becomes the aggregator for that feed. During this process, the `proposed` and `aggregator` subdomains for that feed will change. With each change, the resolver emits an `AddrChanged` event, using the feed subdomain (for example: `eth-usd.data.eth`) as the indexed parameter. **Example**: If you want to listen for when the aggregator of the ETH / USD feed changes, set up a listener to track the `AddrChanged` event on the resolver, using a filter like this: `ethers.utils.namehash('aggregator.eth-usd.data.eth')`. ## Obtaining addresses > **CAUTION: Reverse Lookup** > > Reverse lookup is not supported. ### Javascript The example below uses Javascript Web3 library to interact with ENS. See the [ENS documentation](https://docs.ens.domains/dapp-developer-guide/resolving-names) for the full list of languages and libraries that support ENS. This example logs the address of the data feed on the Ethereum mainnet for ETH / USD prices. ```js const Web3 = require("web3") const web3 = new Web3("https://rpc.ankr.com/eth") web3.eth.ens.getAddress("eth-usd.data.eth").then((address) => { console.log(address) }) ``` ### Solidity In Solidity, the address of the ENS registry must be known. According to [ENS documentation](https://docs.ens.domains/ens-deployments), this address is the same across Mainnet and testnet: ENS registry address: [0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e](https://etherscan.io/address/0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e). Also, instead of using readable string names like `eth-usd.data.eth`, resolvers accept bytes32 hash IDs for names. Hash IDs can be retrieved from [this subgraph](https://thegraph.com/explorer/subgraphs/5XqPmWe6gjyrJtFn9cLy237i4cWw2j9HcUJEXsP5qGtH) or via this npm package [eth-ens-namehash](https://www.npmjs.com/package/@ensdomains/eth-ens-namehash). "ETH / USD" hash: `0xf599f4cd075a34b92169cf57271da65a7a936c35e3f31e854447fbb3e7eb736d` ```sol // SPDX-License-Identifier: MIT pragma solidity ^0.8.7; /** * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY. * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE. * DO NOT USE THIS CODE IN PRODUCTION. */ // ENS Registry Contract interface ENS { function resolver( bytes32 node ) external view returns (Resolver); } // Chainlink Resolver interface Resolver { function addr( bytes32 node ) external view returns (address); } // Consumer contract contract ENSConsumer { ENS ens; // ENS registry address: 0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e constructor( address ensAddress ) { ens = ENS(ensAddress); } // Use ID Hash instead of readable name // ETH / USD hash: 0xf599f4cd075a34b92169cf57271da65a7a936c35e3f31e854447fbb3e7eb736d function resolve( bytes32 node ) public view returns (address) { Resolver resolver = ens.resolver(node); return resolver.addr(node); } } ``` --- # Feed Types Source: https://docs.chain.link/data-feeds/feed-types Chainlink Data Feeds provide multiple feed types for different onchain use cases. | Feed Type | Purpose | | ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Price Feeds](/data-feeds/price-feeds) | Aggregated, decentralized asset prices with cryptographic integrity for collateral, lending, and derivatives. | | [SmartData](/data-feeds/smartdata) | RWA-focused single-value and multi-variable (MVR) feeds (reserves, NAV, AUM, bundled OHLC sets) enabling secure, data-rich tokenization with efficient onchain reads. | | [Smart Value Recapture (SVR) Feeds](/data-feeds/svr-feeds) | Specialized feeds providing metrics that help protocols recapture MEV or reduce external value leakage. | | [Rate and Volatility Feeds](/data-feeds/rates-feeds) | Interest rate curves, staking APR, and realized volatility benchmarks used for pricing models, risk controls, and hedging strategies. | | [L2 Sequencer Uptime Feeds](/data-feeds/l2-sequencer-feeds) | Real-time status of L2 sequencer availability for protective circuit breakers and fallback transaction logic. | | [Self-Managed Feeds](/data-feeds/self-managed-feeds) | Data Feeds where a chain or third party publishes onchain updates instead of Chainlink Labs–managed oracle infrastructure; not operated or monitored by Chainlink Labs. | --- # Consuming Data Feeds Source: https://docs.chain.link/data-feeds/getting-started You can use Chainlink Data Feeds to connect your smart contracts to asset pricing data like the [ETH / USD feed](https://data.chain.link/feeds/ethereum/mainnet/eth-usd). These data feeds use data aggregated from many independent Chainlink node operators. Each price feed has an onchain address and functions that enable contracts to read pricing data from that address. This guide shows you how to read Data Feeds and store the value onchain using Solidity. To learn how to read feeds offchain or use different languages, see the [Using Data Feeds on EVM Chains](/data-feeds/using-data-feeds) guide. Alternatively, you can also learn how to use Data Feeds on [Solana](/data-feeds/solana) or [StarkNet](/data-feeds/starknet). The code for reading Data Feeds on Ethereum or other EVM-compatible blockchains is the same for each chain and each Data Feed types. You choose different types of feeds for different uses, but the request and response format are the same. The answer decimal length and expected value ranges might change depending on what feed you use. > **CAUTION: Using Data Feeds on L2 networks** > > If you are using Chainlink Data Feeds on L2 networks like Arbitrum, OP, and Metis, you must also check the latest > answer from the L2 Sequencer Uptime Feed to ensure that the data is accurate in the event of an L2 sequencer outage. > See the [L2 Sequencer Uptime Feeds](/data-feeds/l2-sequencer-feeds) page to learn how to use Data Feeds on L2 > networks. ## Before you begin If you are new to smart contract development, learn how to [Deploy Your First Smart Contract](/quickstarts/deploy-your-first-contract) before you start this guide. ## Examine the sample contract This example contract obtains the latest price answer from the [BTC / USD feed](/data-feeds/price-feeds/addresses) on the Sepolia testnet, but you can modify it to read any of the different [Types of Data Feeds](/data-feeds#types-of-data-feeds). ```sol // SPDX-License-Identifier: MIT pragma solidity ^0.8.7; import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol"; /** * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED * VALUES FOR CLARITY. * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE. * DO NOT USE THIS CODE IN PRODUCTION. */ /** * If you are reading data feeds on L2 networks, you must * check the latest answer from the L2 Sequencer Uptime * Feed to ensure that the data is accurate in the event * of an L2 sequencer outage. See the * https://docs.chain.link/data-feeds/l2-sequencer-feeds * page for details. */ contract DataConsumerV3 { AggregatorV3Interface internal dataFeed; /** * Network: Sepolia * Aggregator: BTC/USD * Address: 0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43 */ constructor() { dataFeed = AggregatorV3Interface(0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43); } /** * Returns the latest answer. */ function getChainlinkDataFeedLatestAnswer() public view returns (int256) { // prettier-ignore ( /* uint80 roundId */ , int256 answer, /*uint256 startedAt*/ , /*uint256 updatedAt*/ , /*uint80 answeredInRound*/ ) = dataFeed.latestRoundData(); return answer; } } ``` The contract has the following components: - The `import` line imports an interface named `AggregatorV3Interface`. Interfaces define functions without their implementation, which leaves inheriting contracts to define the actual implementation themselves. In this case, `AggregatorV3Interface` defines that all v3 Aggregators have the function `latestRoundData`. You can [see the complete code](https://github.com/smartcontractkit/chainlink/blob/contracts-v1.3.0/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol) for the `AggregatorV3Interface` on GitHub. - The `constructor() {}` initializes an interface object named `dataFeed` that uses `AggregatorV3Interface` and connects specifically to a proxy aggregator contract that is already deployed at `0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43`. The interface allows your contract to run functions on that deployed aggregator contract. - The `getChainlinkDataFeedLatestAnswer()` function calls your `dataFeed` object and runs the `latestRoundData()` function. When you deploy the contract, it initializes the `dataFeed` object to point to the aggregator at `0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43`, which is the proxy address for the Sepolia BTC / USD data feed. Your contract connects to that address and executes the function. The aggregator connects with several oracle nodes and aggregates the pricing data from those nodes. The response from the aggregator includes several variables, but `getChainlinkDataFeedLatestAnswer()` returns only the `answer` variable. ## Compile, deploy, and run the contract > **CAUTION: Configure and fund your wallet** > > If you have not already configured your MetaMask wallet and funded it with testnet ETH, follow the instructions in [Deploy Your First Smart Contract](/quickstarts/deploy-your-first-contract#install-and-fund-your-metamask-wallet) to set that up. You can get testnet ETH at one of the available [Sepolia faucets](/resources/link-token-contracts/#sepolia-testnet). Deploy the `DataConsumerV3` smart contract on the Sepolia testnet. 1. [Open the example contract](https://remix.ethereum.org/#url=https://docs.chain.link/samples/DataFeeds/DataConsumerV3.sol) in Remix. Remix opens and shows the contents of the smart contract. [Open the contract in Remix](https://remix.ethereum.org/#url=https://docs.chain.link/samples/DataFeeds/DataConsumerV3.sol) 2. Because the code is already written, you can start the compile step. On the left side of Remix, click the **Solidity Compiler** tab to view the compiler settings. ![Screenshot showing the Compiler tab and its settings.](/images/getting-started/selectSolidityCompiler.png) 3. Use the default compiler settings. Click the **Compile DataConsumerV3.sol** button to compile the contract. Remix automatically detects the correct compiler version depending on the `pragma` that you specify in the contract. You can ignore warnings about unused local variables in this example. ![Screenshot of the Compile button.](/images/getting-started/compileDataConsumerV3.png) 4. On the **Deploy** tab, select the **Injected Provider** environment. This contract specifically requires Web3 because it connects with another contract on the blockchain. Running in a JavaScript VM will not work. ![Screenshot showing the Injected Provider environment selected.](/images/getting-started/selectWeb3.png) 5. Because the example contract has several imports, Remix might select another contract to deploy by default. In the **Contract** section, select the `DataConsumerV3` contract to make sure that Remix deploys the correct contract. ![Screenshot showing DataConsumerV3 as the contract to deploy.](/images/getting-started/selectDataConsumerV3.png) 6. Click **Deploy** to deploy the contract to the Sepolia testnet. MetaMask opens and asks you to confirm payment for deploying the contract. Make sure MetaMask is set to the Sepolia network before you accept the transaction. Because these transactions are on the blockchain, they are not reversible. ![Screenshot of the Deploy button for DataConsumerV3.](/images/getting-started/deployDataConsumerV3.png) 7. In the MetaMask prompt, click **Confirm** to approve the transaction and spend your testnet ETH required to deploy the contract. ![Screenshot showing Metamask asking you to confirm the transaction.](/images/getting-started/confirmTransaction.png) 8. After a few seconds, the transaction completes and your contract appears under the **Deployed Contracts** list in Remix. Click the contract dropdown to view its variables and functions. ![Remix Deployed Contracts Section](/images/getting-started/deployedContractDataConsumerV3.png) 9. Click **getChainlinkDataFeedLatestAnswer** to show the latest answer from the aggregator contract. In this example, the answer is the latest price, which appears just below the button. The returned answer is an integer, so it is missing its decimal point. You can find the correct number of decimal places for this answer on the [Price Feed addresses](/data-feeds/price-feeds/addresses) page by clicking the **Show more details** checkbox. The answer on the BTC / USD feed uses 8 decimal places, so an answer of `3030914000000` indicates a BTC / USD price of `30309.14`. Each feed uses a different number of decimal places for answers. ![A screenshot showing the deployed contract and the latest answer.](/images/getting-started/getLatestPrice.png) --- # Getting Historical Data Source: https://docs.chain.link/data-feeds/historical-data The most common use case for Data Feeds is to [Get the Latest Data](/data-feeds/using-data-feeds) from a feed. However, the [AggregatorV3Interface.sol](https://github.com/smartcontractkit/chainlink/blob/contracts-v1.3.0/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol) also has functions to retrieve data of a previous round IDs. There are two parameters that can cause Chainlink nodes to update: | Name | Description | | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | Deviation Threshold | Chainlink nodes are monitoring data offchain. The deviation of the real-world data beyond a certain interval triggers all the nodes to update. | | Heartbeat Threshold | If the data values stay within the deviation parameters, it will only trigger an update every *X* minutes / hours. | You can find these parameters at [data.chain.link](https://data.chain.link/) on an example like [ETH / USD](https://data.chain.link/ethereum/mainnet/crypto-usd/eth-usd). To learn how data feeds update, see the [Decentralized Data Model](/architecture-overview/architecture-decentralized-model?parent=dataFeeds#aggregator) page. ## Historical rounds As shown in the [decentralized model](/architecture-overview/architecture-decentralized-model), the consumer contracts call the proxy contract, which abstracts the underlying aggregator contract. The main advantage is to enable upgrades of the aggregator without impacting the consumer contracts. That also means that historical data can can be stored in different aggregators. As shown in the following sequence diagram, to get historical data, call the `getRoundData` [function](/data-feeds/api-reference/#getrounddata) and provide `roundId` as a parameter. ![Image](/images/data-feed/getRoundData-sequence.png) Note that roundIds have different meanings in proxy contracts and in aggregator contracts. ### `roundId` in Aggregator (aggregatorRoundId) Oracles provide periodic data updates to the aggregators. Data feeds are updated in **rounds**. Rounds are identified by their `roundId`, which increases with each new round. This increase may not be monotonic. Knowing the `roundId` of a previous round allows contracts to consume historical data. The examples in this document name the aggregator `roundId` as `aggregatorRoundId` to differentiate it from the proxy `roundId`. ### `roundId` in proxy Because a proxy has references to current and all previous underlying aggregators, it needs a way to fetch data from the correct aggregator. The `roundId` is computed in the [proxy contract](https://github.com/smartcontractkit/chainlink/blob/contracts-v1.0.0/contracts/src/v0.6/AggregatorProxy.sol) as shown in the following example: ```solidity return uint80(uint256(_phase) << PHASE_OFFSET | _originalId); ``` where: - `_phase` is incremented each time the underlying aggregator implementation is updated. It is used as a key to find the aggregator address. - `_originalId` is the aggregator `roundId`. The ID starts at 1. From the above formula, you can think of it as returning a large number containing the `phase` and the aggregator `roundId`. > **NOTE: Note** > > The example formula above ensures that no matter how many times the underlying aggregator changes, the proxy `roundId` > will always increase. **Example:** When you query historical data, it is important to know when you reach the end of the history of the underlying aggregator. As an example, if the [`latestRoundData` function](/data-feeds/api-reference/#latestrounddata) of the [LINK / USD Price Feed on Ethereum Mainnet](/data-feeds/price-feeds/addresses/?network=ethereum) returns `roundId = 92233720368547771158`, you can use this value to compute the `phaseId` and `aggregatorRoundId`: - `phaseId = 92233720368547771158 >> 64`: Right shifting an integer by 64 bits is equivalent to dividing it by 2^64: `phaseId = 92233720368547771158/ 2^64 = 5`. The current phase id is 5 , which means that this proxy has had 5 underlying aggregators since its initial deployment. - `aggregatorRoundId = uint64(92233720368547771158)`: This retrieves the first 64 bits from the right. To calculate this offchain, you can use the following JavaScript example: ```javascript // First parse to BigInt to perform computation with big integers const num = BigInt("92233720368547771158") const num2 = BigInt("0xFFFFFFFFFFFFFFFF") // Largest 64bits integer console.log(Number(num >> 64n)) // returns 5 (phaseId) console.log(Number(num & num2)) // returns 13078 (aggregatorRoundId) . Use & (AND bitwise operator) which sets each bit to _1_ if both bits are _1_ ``` Using *13078* as the current aggregator's round, get its historical data by looping over the [`getRoundData` function](/data-feeds/api-reference/#getrounddata): - Start from the first round: *92233720368547758081* (result of *92233720368547771158 - 13078 + 1*) - Continue until the current round: *92233720368547771158* To get the historical data for previous aggregators, decrement the `phaseId` and start from round *1*. For phase *4*, get the starting `roundId` offchain using the following JavaScript example: ```javascript const phaseId = BigInt("4") const aggregatorRoundId = BigInt("1") roundId = (phaseId << 64n) | aggregatorRoundId // returns 73786976294838206465n ``` Loop over the [`getRoundData` function](/data-feeds/api-reference/#getrounddata). Start at *73786976294838206465* and increment it until you get a revert. This means that you reached the last round for the underlying aggregator. The same process could be repeated for previous `phaseIds` (3,2,1). > **CAUTION: Looping onchain** > > The examples showed how to loop offchain to fetch all historical data from a given proxy. You could also write a > similar code onchain, but be aware that this could cause **very high gas prices** if a state is changed within the > same function. ### `getRoundData` return values The [`getRoundData` function](/data-feeds/api-reference/#getrounddata) returns the following values: - `roundId`: The round in which the answer was updated - `answer`: The answer reflects the data recorded for the specified round - `answeredInRound`: Deprecated - Previously used when answers could take multiple rounds to be computed - `startedAt`: The timestamp when the round started - `updatedAt`: The timestamp when the answer was computed ### Solidity ```sol // SPDX-License-Identifier: MIT pragma solidity ^0.8.7; import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol"; /** * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY. * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE. * DO NOT USE THIS CODE IN PRODUCTION. */ contract HistoricalDataConsumerV3 { AggregatorV3Interface internal dataFeed; /** * Network: Sepolia * Aggregator: ETH/USD * Address: 0x694AA1769357215DE4FAC081bf1f309aDC325306 */ constructor() { dataFeed = AggregatorV3Interface(0x694AA1769357215DE4FAC081bf1f309aDC325306); } /** * Returns historical data for a round ID. * roundId is NOT incremental. Not all roundIds are valid. * You must know a valid roundId before consuming historical data. * * ROUNDID VALUES: * InValid: 18446744073709562300 * Valid: 18446744073709554683 * * @dev A timestamp with zero value means the round is not complete and should not be used. */ function getHistoricalData( uint80 roundId ) public view returns (int256) { // prettier-ignore ( /*uint80 roundID*/ , int256 answer, /*uint startedAt*/ , /*uint timeStamp*/ , /*uint80 answeredInRound*/ ) = dataFeed.getRoundData(roundId); return answer; } } ``` ### Javascript ```js /** * THIS IS EXAMPLE CODE THAT USES HARDCODED VALUES FOR CLARITY. * THIS IS EXAMPLE CODE THAT USES UN-AUDITED CODE. * DO NOT USE THIS CODE IN PRODUCTION. */ const Web3 = require("web3") // for nodejs only // Replace the provider URL with your own endpoint URL const web3 = new Web3("https://rpc.ankr.com/eth_sepolia") const aggregatorV3InterfaceABI = [ { inputs: [], name: "decimals", outputs: [{ internalType: "uint8", name: "", type: "uint8" }], stateMutability: "view", type: "function", }, { inputs: [], name: "description", outputs: [{ internalType: "string", name: "", type: "string" }], stateMutability: "view", type: "function", }, { inputs: [{ internalType: "uint80", name: "_roundId", type: "uint80" }], name: "getRoundData", outputs: [ { internalType: "uint80", name: "roundId", type: "uint80" }, { internalType: "int256", name: "answer", type: "int256" }, { internalType: "uint256", name: "startedAt", type: "uint256" }, { internalType: "uint256", name: "updatedAt", type: "uint256" }, { internalType: "uint80", name: "answeredInRound", type: "uint80" }, ], stateMutability: "view", type: "function", }, { inputs: [], name: "latestRoundData", outputs: [ { internalType: "uint80", name: "roundId", type: "uint80" }, { internalType: "int256", name: "answer", type: "int256" }, { internalType: "uint256", name: "startedAt", type: "uint256" }, { internalType: "uint256", name: "updatedAt", type: "uint256" }, { internalType: "uint80", name: "answeredInRound", type: "uint80" }, ], stateMutability: "view", type: "function", }, { inputs: [], name: "version", outputs: [{ internalType: "uint256", name: "", type: "uint256" }], stateMutability: "view", type: "function", }, ] const addr = "0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43" const dataFeed = new web3.eth.Contract(aggregatorV3InterfaceABI, addr) // Valid roundId must be known. They are NOT incremental. let validId = BigInt("18446744073709554177") dataFeed.methods .getRoundData(validId) .call() .then((historicalRoundData) => { document.getElementById("get-data-field").value = historicalRoundData.answer }) ``` ### Python ```py # THIS IS EXAMPLE CODE THAT USES HARDCODED VALUES FOR CLARITY. # THIS IS EXAMPLE CODE THAT USES UN-AUDITED CODE. # DO NOT USE THIS CODE IN PRODUCTION. from web3 import Web3 # Change this to use your own RPC URL web3 = Web3(Web3.HTTPProvider('https://rpc.ankr.com/eth_sepolia')) # AggregatorV3Interface ABI abi = '[{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"description","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint80","name":"_roundId","type":"uint80"}],"name":"getRoundData","outputs":[{"internalType":"uint80","name":"roundId","type":"uint80"},{"internalType":"int256","name":"answer","type":"int256"},{"internalType":"uint256","name":"startedAt","type":"uint256"},{"internalType":"uint256","name":"updatedAt","type":"uint256"},{"internalType":"uint80","name":"answeredInRound","type":"uint80"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestRoundData","outputs":[{"internalType":"uint80","name":"roundId","type":"uint80"},{"internalType":"int256","name":"answer","type":"int256"},{"internalType":"uint256","name":"startedAt","type":"uint256"},{"internalType":"uint256","name":"updatedAt","type":"uint256"},{"internalType":"uint80","name":"answeredInRound","type":"uint80"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"version","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]' # Feed address addr = '0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43' # Set up contract instance contract = web3.eth.contract(address=addr, abi=abi) # Valid roundId must be known. They are NOT incremental. # invalidRoundId = 18446744073709562300 validRoundId = 18446744073709554177 historicalData = contract.functions.getRoundData(validRoundId).call() print(historicalData) ``` --- # Chainlink Data Feeds Source: https://docs.chain.link/data-feeds Last Updated: 2026-09-16 --- # L2 Sequencer Uptime Feeds Source: https://docs.chain.link/data-feeds/l2-sequencer-feeds > **CAUTION: Availability notice** > > Chainlink is no longer expanding L2 Sequencer Uptime Feeds to additional networks. Existing feeds on the supported > networks listed below will continue to operate and be supported. Optimistic rollups (e.g., Arbitrum, Optimism) and many ZK-rollups rely on sequencers to efficiently manage transaction ordering, execution, and batching before submitting them to Layer 1 (L1) blockchains like Ethereum. The sequencer plays a crucial role in optimizing transaction throughput, reducing fees, and ensuring fast transaction confirmations on L2 networks, making it a key component of their scalability and performance. However, if the sequencer becomes unavailable, users will lose access to the standard read/write APIs, preventing them from interacting with applications on the L2 network. Although the L2 chain's security and state commitments remain enforced by Layer 1, no new batched blocks will be produced by the sequencer. Users with sufficient technical expertise can still interact directly with the network through the underlying rollup contracts on L1. However, this process is more complex and costly, creating an unfair advantage for those who can bypass the sequencer. This imbalance in access can lead to disruptions or distortions in applications, such as liquidations or market operations that rely on timely transactions. To mitigate these risks, your applications can integrate a **Sequencer Uptime Data Feed**, which continuously monitors and records the last known status of the sequencer. By utilizing this feed, you can: - Detect sequencer downtime in real time. - Implement a grace period to prevent mass liquidations or unexpected disruptions. - Ensure fair access to services by temporarily pausing operations during sequencer failures. This proactive approach enhances the resilience and fairness of applications operating on L2 networks, ensuring a more stable and equitable user experience. ## Supported Networks You can find proxy addresses for the L2 sequencer feeds at the following addresses: ### Arbitrum Arbitrum Mainnet: [0xFdB631F5EE196F0ed6FAa767959853A9F217697D](https://arbiscan.io/address/0xFdB631F5EE196F0ed6FAa767959853A9F217697D) ### BASE BASE Mainnet: [0xBCF85224fc0756B9Fa45aA7892530B47e10b6433](https://basescan.org/address/0xBCF85224fc0756B9Fa45aA7892530B47e10b6433) ### Celo Celo Mainnet: [0x4CD491Dc27C8B0BbD10D516A502856B786939d18](https://celoscan.io/address/0x4CD491Dc27C8B0BbD10D516A502856B786939d18) ### Mantle Mantle Mainnet: [0xaDE1b9AbB98c6A542E4B49db2588a3Ec4bF7Cdf0](https://mantlescan.xyz/address/0xaDE1b9AbB98c6A542E4B49db2588a3Ec4bF7Cdf0) ### MegaETH MegaETH Mainnet: [0x78B2195A21B8BBe82acaB43F90F9180E9513FD0C](https://mega.etherscan.io/address/0x78B2195A21B8BBe82acaB43F90F9180E9513FD0C) ### Metis Andromeda Mainnet: [0x58218ea7422255EBE94e56b504035a784b7AA204](https://andromeda-explorer.metis.io/address/0x58218ea7422255EBE94e56b504035a784b7AA204) ### OP OP Mainnet: [0x371EAD81c9102C9BF4874A9075FFFf170F2Ee389](https://optimistic.etherscan.io/address/0x371EAD81c9102C9BF4874A9075FFFf170F2Ee389) ### Scroll Scroll Mainnet: [0x45c2b8C204568A03Dc7A2E32B71D67Fe97F908A9](https://scrollscan.com/address/0x45c2b8C204568A03Dc7A2E32B71D67Fe97F908A9) ### Soneium Soneium Mainnet: [0xaDE1b9AbB98c6A542E4B49db2588a3Ec4bF7Cdf0](https://soneium.blockscout.com/address/0xaDE1b9AbB98c6A542E4B49db2588a3Ec4bF7Cdf0) ### X Layer X Layer Mainnet: [0x45c2b8C204568A03Dc7A2E32B71D67Fe97F908A9](https://www.okx.com/web3/explorer/xlayer/address/0x45c2b8C204568A03Dc7A2E32B71D67Fe97F908A9) ### ZKsync zkSync Mainnet: [0x0E6AC8B967393dcD3D36677c126976157F993940](https://explorer.zksync.io/address/0x0E6AC8B967393dcD3D36677c126976157F993940) ## Real-time Monitoring Process ### Arbitrum The diagram below shows how these feeds update and how a consumer retrieves the status of the Arbitrum sequencer. ![Image](/images/data-feed/l2-diagram-arbitrum.webp) 1. Chainlink nodes trigger an OCR round every 30s and update the sequencer status by calling the `validate` function in the [`ArbitrumValidator` contract](https://github.com/smartcontractkit/chainlink/blob/contracts-v1.3.0/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumValidator.sol) by calling it through the [`ValidatorProxy` contract](https://github.com/smartcontractkit/chainlink/blob/contracts-v1.0.0/contracts/src/v0.8/ValidatorProxy.sol). 2. The `ArbitrumValidator` checks to see if the latest update is different from the previous update. If it detects a difference, it places a message in the [Arbitrum inbox contract](https://docs.arbitrum.io/how-arbitrum-works/inside-arbitrum-nitro). 3. The inbox contract sends the message to the [`ArbitrumSequencerUptimeFeed` contract](https://github.com/smartcontractkit/chainlink/blob/contracts-v1.3.0/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumSequencerUptimeFeed.sol). The message calls the `updateStatus` function in the `ArbitrumSequencerUptimeFeed` contract and updates the latest sequencer status to 0 if the sequencer is up and 1 if it is down. It also records the block timestamp to indicate when the message was sent from the L1 network. 4. A consumer contract on the L2 network can read these values from the [`ArbitrumUptimeFeedProxy` contract](https://github.com/smartcontractkit/chainlink/blob/contracts-v1.0.0/contracts/src/v0.6/EACAggregatorProxy.sol), which reads values from the `ArbitrumSequencerUptimeFeed` contract. #### Handling Arbitrum Outages If the Arbitrum network becomes unavailable, the `ArbitrumValidator` contract continues to send messages to the L2 network through the delayed inbox on L1. This message stays there until the sequencer is back up again. When the sequencer comes back online after downtime, it processes all transactions from the delayed inbox before it accepts new transactions. The message that signals when the sequencer is down will be processed before any new messages with transactions that require the sequencer to be operational. ### Other Supported Networks On BASE, Celo, Mantle, Metis, OP, Scroll, Soneium and zkSync, the sequencer's status is relayed from L1 to L2 where the consumer can retrieve it. ![Image](/images/data-feed/l2-diagram-optimism-metis.webp) **On the L1 network:** 1. A network of node operators runs the external adapter to post the latest sequencer status to the `AggregatorProxy` contract and relays the status to the `Aggregator` contract. The `Aggregator` contract calls the `validate` function in the `OptimismValidator` contract. 2. The `OptimismValidator` contract calls the `sendMessage` function in the `L1CrossDomainMessenger` contract. This message contains instructions to call the `updateStatus(bool status, uint64 timestamp)` function in the sequencer uptime feed deployed on the L2 network. 3. The `L1CrossDomainMessenger` contract calls the `enqueue` function to enqueue a new message to the `CanonicalTransactionChain`. 4. The `Sequencer` processes the transaction enqueued in the `CanonicalTransactionChain` contract to send it to the L2 contract. **On the L2 network:** 1. The `Sequencer` posts the message to the `L2CrossDomainMessenger` contract. 2. The `L2CrossDomainMessenger` contract relays the message to the `OptimismSequencerUptimeFeed` contract. 3. The message relayed by the `L2CrossDomainMessenger` contains instructions to call `updateStatus` in the `OptimismSequencerUptimeFeed` contract. 4. Consumers can then read from the `AggregatorProxy` contract, which fetches the latest round data from the `OptimismSequencerUptimeFeed` contract. #### Handling Other Supported Network Outages If the sequencer is down, messages cannot be transmitted from L1 to L2 and **no L2 transactions are executed**. Instead, messages are enqueued in the `CanonicalTransactionChain` on L1 and only processed in the order they arrived later when the sequencer comes back up. As long as the message from the validator on L1 is already enqueued in the `CTC`, the flag on the sequencer uptime feed on L2 will be guaranteed to be flipped prior to any subsequent transactions. The transaction that flips the flag on the uptime feed will be executed before transactions that were enqueued after it. This is further explained in the diagrams below. When the Sequencer is down, all L2 transactions sent from the L1 network wait in the pending queue. 1. **Transaction 3** contains Chainlink's transaction to set the status of the sequencer as being down on L2. 2. **Transaction 4** is a transaction made by a consumer that is dependent on the sequencer status. ![Image](/images/data-feed/seq-down-1.webp) After the sequencer comes back up, it moves all transactions in the pending queue to the processed queue. 1. Transactions are processed in the order they arrived so **Transaction 3** is processed before **Transaction 4**. 2. Because **Transaction 3** happens before **Transaction 4**, **Transaction 4** will read the status of the Sequencer as being down and responds accordingly. ![Image](/images/data-feed/seq-down-2.webp) ## Example Consumer Contract This example code works on any network that supports Solidity. Create the consumer contract for sequencer uptime feeds similarly to the contracts that you use for other [Chainlink Data Feeds](/data-feeds/using-data-feeds). Configure the constructor using the following variables: - Configure the `sequencerUptimeFeed` object with the [sequencer uptime feed proxy address](#supported-networks) for your L2 network. - Configure the `dataFeed` object with one of the [Data Feed proxy addresses](/data-feeds/price-feeds/addresses) that are available for your network. ```sol // SPDX-License-Identifier: MIT pragma solidity ^0.8.7; import {AggregatorV2V3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV2V3Interface.sol"; /** * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY. * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE. * DO NOT USE THIS CODE IN PRODUCTION. */ contract DataConsumerWithSequencerCheck { AggregatorV2V3Interface internal dataFeed; AggregatorV2V3Interface internal sequencerUptimeFeed; uint256 private constant GRACE_PERIOD_TIME = 3600; error SequencerDown(); error GracePeriodNotOver(); /** * Network: OP Mainnet * Data Feed: BTC/USD * Data Feed address: 0xD702DD976Fb76Fffc2D3963D037dfDae5b04E593 * Uptime Feed address: 0x371EAD81c9102C9BF4874A9075FFFf170F2Ee389 * For a list of available Sequencer Uptime Feed proxy addresses, see: * https://docs.chain.link/docs/data-feeds/l2-sequencer-feeds */ constructor() { dataFeed = AggregatorV2V3Interface(0xD702DD976Fb76Fffc2D3963D037dfDae5b04E593); sequencerUptimeFeed = AggregatorV2V3Interface(0x371EAD81c9102C9BF4874A9075FFFf170F2Ee389); } // Check the sequencer status and return the latest data function getChainlinkDataFeedLatestAnswer() public view returns (int256) { // prettier-ignore ( /*uint80 roundID*/ , int256 answer, uint256 startedAt, /*uint256 updatedAt*/ , /*uint80 answeredInRound*/ ) = sequencerUptimeFeed.latestRoundData(); // Answer == 0: Sequencer is up // Answer == 1: Sequencer is down bool isSequencerUp = answer == 0; if (!isSequencerUp) { revert SequencerDown(); } // Make sure the grace period has passed after the // sequencer is back up. uint256 timeSinceUp = block.timestamp - startedAt; if (timeSinceUp <= GRACE_PERIOD_TIME) { revert GracePeriodNotOver(); } // prettier-ignore ( /*uint80 roundID*/ , int256 data, /*uint startedAt*/ , /*uint timeStamp*/ , /*uint80 answeredInRound*/ ) = dataFeed.latestRoundData(); return data; } } ``` The `sequencerUptimeFeed` object returns the following values: - `answer`: A variable with a value of either `0` or `1` - 0: The sequencer is up - 1: The sequencer is down - `startedAt`: This timestamp indicates when the sequencer feed changed status. When the sequencer comes back up after an outage, wait for the `GRACE_PERIOD_TIME` to pass before accepting answers from the data feed. Subtract `startedAt` from `block.timestamp` and revert the request if the result is less than the `GRACE_PERIOD_TIME`. - The `startedAt` variable returns `0` only on Arbitrum when the Sequencer Uptime contract is not yet initialized. For L2 chains other than Arbitrum, `startedAt` is set to `block.timestamp` on construction and `startedAt` is never `0`. After the feed begins rounds, the `startedAt` timestamp will always indicate when the sequencer feed last changed status. If the sequencer is up and the `GRACE_PERIOD_TIME` has passed, the function retrieves the latest answer from the data feed using the `dataFeed` object. --- # MVR Feeds API Reference Source: https://docs.chain.link/data-feeds/mvr-feeds/api-reference When you use MVR Feeds, you typically retrieve the data through the `IBundleAggregatorProxy` interface and the proxy contract address. This interface combines functions from both `IBundleBaseAggregator` and `ICommonAggregator`. If the aggregator behind the proxy changes, your consumer contract remains compatible as long as you interact through the proxy interface. To see detailed examples of how to integrate these functions, refer to the [Using MVR Feeds on EVM Chains (Solidity)](/data-feeds/mvr-feeds/guides/evm-solidity) guide. ## IBundleAggregatorProxy `IBundleAggregatorProxy` extends both `IBundleBaseAggregator` and `ICommonAggregator`. In addition, it provides functions to manage or view the underlying aggregator reference. A minimal example showing how to set the proxy address and call these functions in your consumer contract: ```solidity import { IBundleAggregatorProxy } from "./interfaces/IBundleAggregatorProxy.sol"; contract MyMVRConsumer { IBundleAggregatorProxy public proxy; constructor(address _proxyAddress) { proxy = IBundleAggregatorProxy(_proxyAddress); } function readLatestData() external view returns (bytes memory, uint256) { // Retrieve the latest bundle as raw bytes bytes memory bundle = proxy.latestBundle(); // Get the timestamp of the latest update uint256 timestamp = proxy.latestBundleTimestamp(); return (bundle, timestamp); } } ``` ### Functions in IBundleAggregatorProxy | Name | Description | | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | [proposedAggregator](#proposedaggregator) | Returns the address of the aggregator that is proposed to replace the current aggregator, but not yet confirmed. | | [confirmAggregator](#confirmaggregator) | Confirms a new aggregator address. Typically, only the owner or an authorized entity can call this. | | [aggregator](#aggregator) | Returns the address of the currently confirmed aggregator behind the proxy. | | [latestBundle](#latestbundle) | Returns the most recent data bundle as a bytes array. | | [bundleDecimals](#bundledecimals) | Returns an array of decimals that match each numeric field in the bundle. Non-numeric fields typically have 0. | | [latestBundleTimestamp](#latestbundletimestamp) | Returns the timestamp of the most recent data bundle. | | [description](#description) | A short description of the underlying aggregator or feed. | | [version](#version) | Returns a version number for the aggregator. | #### proposedAggregator ```solidity function proposedAggregator() external view returns (address); ``` Returns the address of the aggregator that is proposed to replace the current aggregator. This aggregator is not active until it is confirmed. #### confirmAggregator ```solidity function confirmAggregator(address aggregatorAddress) external; ``` Confirms a new aggregator contract address. Only authorized addresses can typically call this function. - Parameters: - aggregatorAddress: The address of the new aggregator to confirm. #### aggregator ```solidity function aggregator() external view returns (address); ``` Returns the address of the currently confirmed aggregator behind the proxy. #### latestBundle ```solidity function latestBundle() external view returns (bytes memory bundle); ``` Returns the most recent data bundle as a bytes array. You can decode this bundle according to the known structure for your specific MVR feed. #### bundleDecimals ```solidity function bundleDecimals() external view returns (uint8[] memory); ``` Returns an array of decimals for the numeric fields of the data bundle. Fields of other types, such as bool, typically have a corresponding decimal value of 0. #### latestBundleTimestamp ```solidity function latestBundleTimestamp() external view returns (uint256); ``` Returns the timestamp (block time) of the most recent update to the data bundle. #### description ```solidity function description() external view returns (string memory); ``` Returns a short description of the underlying aggregator or feed. #### version ```solidity function version() external view returns (uint256); ``` Returns the version number of the aggregator behind the proxy. *** ## IBundleBaseAggregator `IBundleBaseAggregator` defines the core functions for reading multiple-variable data from an onchain aggregator. These functions are inherited by `IBundleAggregatorProxy`. ### Functions in IBundleBaseAggregator | Name | Description | | --------------------- | ------------------------------------------------------------------ | | latestBundle | Returns the most recent data bundle as a bytes array. | | bundleDecimals | Returns the array of decimals corresponding to each numeric field. | | latestBundleTimestamp | Returns the timestamp of the most recent data bundle. | #### latestBundle ```solidity function latestBundle() external view returns (bytes memory bundle); ``` Returns the latest encoded bundle data as a bytes array. #### bundleDecimals ```solidity function bundleDecimals() external view returns (uint8[] memory); ``` Returns an array of decimals where each entry corresponds to a numeric field in the bundle. Non-numeric fields might have a value of 0. #### latestBundleTimestamp ```solidity function latestBundleTimestamp() external view returns (uint256); ``` Returns the block timestamp of the most recent data bundle. *** ## ICommonAggregator `ICommonAggregator` provides common metadata functions for aggregator contracts, such as a human-readable description and a version number. ### Functions in ICommonAggregator | Name | Description | | ----------- | -------------------------------------------------------- | | description | A string describing the underlying aggregator. | | version | Returns a numeric version identifier for the aggregator. | #### description ```solidity function description() external view returns (string memory); ``` Returns a description string for the feed or aggregator. #### version ```solidity function version() external view returns (uint256); ``` Returns a numeric value representing the aggregator version. --- # Using MVR Feeds with ethers.js (JS) Source: https://docs.chain.link/data-feeds/mvr-feeds/guides/ethersjs This guide explains how to use [Multiple-Variable Response (MVR) feeds](/data-feeds/mvr-feeds) data in your JavaScript applications using the [ethers.js v5 library](https://docs.ethers.org/v5/). MVR feeds store multiple data points in a single byte array onchain. To consume this data in your JavaScript application: 1. **Obtain the proxy address and data structure**: - Find the `BundleAggregatorProxy` address for the specific MVR feed you want to read on the [SmartData Addresses](/data-feeds/smartdata/addresses?page=1#networks) page - Expand the "MVR Bundle Info" section to see the exact data structure, field types, and decimals - Note these details as you'll need to match this structure exactly in your code 2. **Set up ethers.js**: Create a provider and contract instance to interact with the feed. 3. **Check data staleness**: Compare the feed's latest timestamp against current time to verify it hasn't exceeded your maximum acceptable staleness threshold. 4. **Fetch and decode the data**: Retrieve the feed's latest bundle and decode the bytes array. 5. **Apply decimals**: Scale numeric values to their true decimal representation for accurate calculations and display. 6. **Use in your application**: Process or display the decoded values. > **CAUTION: Disclaimer** > > This guide represents an example of using a Chainlink product or service and is provided to help you understand how to > interact with Chainlink's systems and services so that you can integrate them into your own. This template is provided > "AS IS" and "AS AVAILABLE" without warranties of any kind, has not been audited, and may be missing key checks or > error handling to make the usage of the product more clear. Do not use the code in this example in a production > environment without completing your own audits and application of best practices. Neither Chainlink Labs, the > Chainlink Foundation, nor Chainlink node operators are responsible for unintended outputs that are generated due to > errors in code. ## Prerequisites - [Node.js](https://nodejs.org/en/download/) environment (Node.js >=12.x recommended) - [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) or [yarn](https://yarnpkg.com/getting-started) installed - [ethers.js](https://docs.ethers.org/v5/) library v5.x installed: ```shell npm install ethers@^5.0.0 ``` or ```shell yarn add ethers@^5.0.0 ``` **Note:** For [ethers.js v6](https://docs.ethers.org/v6/), some API calls differ significantly. - An RPC URL for the network where the MVR feed is deployed. You can sign up for a personal endpoint from [Alchemy](https://www.alchemy.com/) or [Infura](https://infura.io/). - Set up environment variables: ```shell npm install dotenv ``` or ```shell yarn add dotenv ``` ## Step-by-Step Implementation ### 1. Define the BundleAggregatorProxy Interface First, define the minimal ABI or interface for the `BundleAggregatorProxy` contract: ```javascript const bundleAggregatorProxyABI = [ "function latestBundle() external view returns (bytes)", "function bundleDecimals() external view returns (uint8[])", "function latestBundleTimestamp() external view returns (uint256)", ] ``` ### 2. Set Up the Provider and Contract Instance Connect to a blockchain provider and create the contract instance, using environment variables for sensitive information: ```javascript // Load environment variables (in Node.js) require("dotenv").config() const { ethers } = require("ethers") // Get RPC URL from environment variables const rpcUrl = process.env.RPC_URL if (!rpcUrl) { throw new Error("RPC_URL not found in environment variables") } // Connect to a provider securely const provider = new ethers.providers.JsonRpcProvider(rpcUrl) // MVR Feed proxy address - replace with the actual address for your feed // This can also be stored in environment variables for production const proxyAddress = process.env.MVR_FEED_ADDRESS || "0x..." // Create contract instance const mvrFeed = new ethers.Contract(proxyAddress, bundleAggregatorProxyABI, provider) ``` Create a `.env` file in your project root (and add it to `.gitignore`): ``` # .env RPC_URL= MVR_FEED_ADDRESS= ``` ### 3. Validate Data Staleness Before using the data, check its timestamp to ensure it's not stale: ```javascript /** * Checks if the feed data is fresh enough to use * @returns {Promise} True if data is not stale * @throws {Error} If data is stale */ async function checkDataStaleness() { // Get the latest timestamp const lastUpdateTime = await mvrFeed.latestBundleTimestamp() const timestamp = lastUpdateTime.toNumber() // Current time in seconds const now = Math.floor(Date.now() / 1000) // Define staleness threshold const stalenessThreshold = 86400 // 24 hours in seconds if (now - timestamp > stalenessThreshold) { throw new Error(`Data is stale. Last update was ${now - timestamp} seconds ago.`) } return true } ``` **Important**: Don't use arbitrary values for staleness thresholds. The appropriate threshold should be determined by: 1. Find the feed's **heartbeat interval** on the [SmartData Addresses](/data-feeds/smartdata/addresses?page=1#networks) page (click "Show more details") 2. Set a threshold that aligns with this interval, usually the heartbeat plus a small buffer 3. Consider your specific use case requirements (some applications may need very recent data) ### 4. Fetch and Decode the Bundle Data > **NOTE: MVR Feed-Specific Data Structure** > > Find the exact data structure for your specific MVR feed on the [SmartData > Addresses](/data-feeds/smartdata/addresses?page=1#networks) page. Click on the feed entry and expand the "MVR Bundle > Info" section to see all fields, their types, and decimals. MVR feeds encode multiple data points in a single bytes array. You need to know the structure to properly decode it: ```javascript // Define the data structure based on the specific feed's format // This MUST match the format defined in the feed documentation const dataStructure = [ "uint256", // netAssetValue "uint256", // assetsUnderManagement "uint256", // outstandingShares "uint256", // netIncomeExpenses "bool", // openToNewInvestors ] // Field names for easier access const fieldNames = [ "netAssetValue", "assetsUnderManagement", "outstandingShares", "netIncomeExpenses", "openToNewInvestors", ] /** * Gets the raw decoded data from the feed * @returns {Promise} The decoded data with BigNumber values */ async function getRawData() { try { // First check data staleness await checkDataStaleness() // Get raw bundle data const bundleBytes = await mvrFeed.latestBundle() // Decode the bytes array using ethers.js utilities const decodedValues = ethers.utils.defaultAbiCoder.decode(dataStructure, bundleBytes) // Create a more accessible object with named fields const result = {} fieldNames.forEach((name, index) => { if (index < decodedValues.length) { result[name] = decodedValues[index] } }) return result } catch (error) { console.error("Error fetching or decoding data:", error) throw error } } ``` ### 5. Apply Decimal Scaling Factors > **NOTE: Robust Decimal Handling** > > Different RPC providers might return decimals in varying formats. The robust implementation shown here handles > different possible return types, including arrays of BigNumber objects, plain arrays, or objects with numeric > properties. You need to apply the appropriate decimals to convert the raw fixed-point integers to their true numerical values: ```javascript /** * Gets formatted data with decimal adjustments for accurate numerical representation * @returns {Promise} The data with correct decimal scaling applied */ async function getFormattedData() { try { // Get raw decoded data const rawData = await getRawData() // Get decimals for each field const decimalsArray = await mvrFeed.bundleDecimals() // Process data with decimals const formattedData = {} fieldNames.forEach((name, index) => { // Skip boolean values - they don't need decimal adjustment if (index < dataStructure.length && dataStructure[index] === "bool") { formattedData[name] = rawData[name] return } // Verify the value exists and is a BigNumber const value = rawData[name] if (!value || !ethers.BigNumber.isBigNumber(value)) { formattedData[name] = { raw: value, formatted: String(value ?? ""), decimals: 0, } return } // Get decimal places from the array const decimalPlaces = index < decimalsArray.length ? Number(decimalsArray[index]) : 0 const divisor = ethers.BigNumber.from(10).pow(decimalPlaces) // Store both raw and formatted values formattedData[name] = { raw: value, // Original BigNumber value: value.div(divisor), // BigNumber after decimal adjustment decimals: decimalPlaces, // Add a formatted string for display purposes using our helper function formatted: formatWithDecimals(value, decimalPlaces), } }) return formattedData } catch (error) { console.error("Error formatting data:", error) throw new Error(`Failed to format MVR feed data: ${error.message}`) } } ``` ### 6. Convert to Human-Readable Format (Optional) For display purposes, convert BigNumber values to strings using ethers.js built-in formatters: ```javascript /** * Formats a BigNumber with the appropriate number of decimals * @param {ethers.BigNumber} value - The value to format * @param {number} decimals - The number of decimal places * @returns {string} A formatted string representation */ function formatWithDecimals(value, decimals) { // Handle non-BigNumber values if (!value || !ethers.BigNumber.isBigNumber(value)) { return String(value ?? "") } // Use ethers.js built-in formatter for consistent, reliable formatting return ethers.utils.formatUnits(value, decimals ?? 0) } ``` This uses [`ethers.utils.formatUnits()`](https://docs.ethers.org/v5/api/utils/display-logic/), which is specifically designed to format numbers with the correct decimal places. The function handles different value types and applies the appropriate formatting based on the data structure. ## Complete Example Here's a complete example that ties everything together into a reusable class: ```javascript // Load environment variables require("dotenv").config() const { ethers } = require("ethers") class MVRFeedClient { /** * Creates a new MVR Feed client * @param {string} proxyAddress - The address of the BundleAggregatorProxy contract * @param {ethers.providers.Provider} provider - An ethers.js provider * @param {object} config - Configuration options * @param {number} config.stalenessThreshold - The staleness threshold in seconds * @param {string[]} config.dataStructure - The ABI types for the data structure * @param {string[]} config.fieldNames - Names for each field in the data structure */ constructor(proxyAddress, provider, config = {}) { if (!proxyAddress) { throw new Error("Proxy address is required") } if (!provider) { throw new Error("Provider is required") } // Set up contract ABI const bundleAggregatorProxyABI = [ "function latestBundle() external view returns (bytes)", "function bundleDecimals() external view returns (uint8[])", "function latestBundleTimestamp() external view returns (uint256)", ] // Create contract instance this.contract = new ethers.Contract(proxyAddress, bundleAggregatorProxyABI, provider) // Configure data structure this.dataStructure = config.dataStructure ?? [ "uint256", // netAssetValue "uint256", // assetsUnderManagement "uint256", // outstandingShares "uint256", // netIncomeExpenses "bool", // openToNewInvestors ] this.fieldNames = config.fieldNames ?? [ "netAssetValue", "assetsUnderManagement", "outstandingShares", "netIncomeExpenses", "openToNewInvestors", ] // Set staleness threshold with nullish coalescing this.stalenessThreshold = config.stalenessThreshold ?? 86400 // 24 hours default } /** * Checks if the data hasn't exceeded the staleness threshold based on the timestamp * @returns {Promise} True if data is not stale * @throws {Error} If data is stale */ async checkDataStaleness() { const timestamp = (await this.contract.latestBundleTimestamp()).toNumber() const now = Math.floor(Date.now() / 1000) if (now - timestamp > this.stalenessThreshold) { throw new Error(`Data is stale. Last update was ${now - timestamp} seconds ago.`) } return true } /** * Gets the raw decoded data from the feed * @returns {Promise} The decoded data with BigNumber values */ async getRawData() { try { // First check data staleness await this.checkDataStaleness() // Get raw bundle data const bundleBytes = await this.contract.latestBundle() // Decode the bytes array using ethers.js utilities const decodedValues = ethers.utils.defaultAbiCoder.decode(this.dataStructure, bundleBytes) // Create a more accessible object with named fields const result = {} this.fieldNames.forEach((name, index) => { if (index < decodedValues.length) { result[name] = decodedValues[index] } }) return result } catch (error) { console.error("Error fetching or decoding data:", error) throw error } } /** * Gets formatted data with decimal adjustments for accurate numerical representation * @returns {Promise} The data with correct decimal scaling applied */ async getFormattedData() { try { const rawData = await this.getRawData() const decimalsArray = await this.contract.bundleDecimals() const formattedData = {} this.fieldNames.forEach((name, index) => { // Skip boolean values - they don't need decimal adjustment if (index < this.dataStructure.length && this.dataStructure[index] === "bool") { formattedData[name] = rawData[name] return } // Verify the value exists and is a BigNumber const value = rawData[name] if (!value || !ethers.BigNumber.isBigNumber(value)) { formattedData[name] = { raw: value, formatted: String(value ?? ""), decimals: 0, } return } // Get decimal places from the array const decimalPlaces = index < decimalsArray.length ? Number(decimalsArray[index]) : 0 const divisor = ethers.BigNumber.from(10).pow(decimalPlaces) formattedData[name] = { raw: value, value: value.div(divisor), // Properly scaled for calculations decimals: decimalPlaces, // Add a formatted string for display purposes using our helper function formatted: this.formatWithDecimals(value, decimalPlaces), } }) return formattedData } catch (error) { console.error("Error formatting data:", error) throw new Error(`Failed to format MVR feed data: ${error.message}`) } } /** * Formats a BigNumber with the appropriate number of decimals * @param {ethers.BigNumber} value - The value to format * @param {number} decimals - The number of decimal places * @returns {string} A formatted string representation */ formatWithDecimals(value, decimals) { // Handle non-BigNumber values if (!value || !ethers.BigNumber.isBigNumber(value)) { return String(value ?? "") } // Use ethers.js built-in formatter for consistent, reliable formatting return ethers.utils.formatUnits(value, decimals ?? 0) } } // Example usage async function main() { // Get configuration from environment variables const rpcUrl = process.env.RPC_URL if (!rpcUrl) { throw new Error("RPC_URL not found in environment variables") } const feedAddress = process.env.MVR_FEED_ADDRESS if (!feedAddress) { throw new Error("MVR_FEED_ADDRESS environment variable not set") } const provider = new ethers.providers.JsonRpcProvider(rpcUrl) const mvrClient = new MVRFeedClient(feedAddress, provider) try { const data = await mvrClient.getFormattedData() console.log("MVR Feed Data:") // Display the data for (const [key, value] of Object.entries(data)) { if (typeof value === "boolean") { console.log(`${key}: ${value}`) } else { console.log(`${key}: ${value.formatted} (raw: ${value.raw.toString()})`) } } } catch (error) { console.error("Error fetching MVR data:", error.message) } } main() ``` ### Customizing and Running the Example To run the complete example above: 1. Find the MVR feed you want to read on the [SmartData Addresses](/data-feeds/smartdata/addresses) page - Copy the `BundleAggregatorProxy` address for the next step - Expand the "MVR Bundle Info" section to see all fields, their types, and decimals 2. Create a `.env` file in your project directory: ``` RPC_URL= MVR_FEED_ADDRESS= ``` 3. Update the code to match your specific MVR feed: ```javascript // IMPORTANT: Always match the structure to your specific MVR feed this.dataStructure = [ // Replace with your feed's exact types in correct order "uint256", // Example: netAssetValue "uint256", // Example: assetsUnderManagement "bool", // Example: openToNewInvestors ] this.fieldNames = [ // Replace with your feed's exact field names in same order "netAssetValue", "assetsUnderManagement", "openToNewInvestors", ] ``` 4. Install the required dependencies: ```bash npm install ethers@^5.0.0 dotenv ``` 5. Run the application: ```bash node your-script-filename.js ``` The output should display the formatted data from the MVR feed with both formatted values and raw values. ## Key Points - **ABI Definition**: Ensure your ABI defines the expected functions. - **Data Structure**: The data structure must exactly match the feed's format. - **Staleness Check**: Always verify data staleness using the timestamp. - **Decimal Scaling**: Apply the correct decimals to convert fixed-point integers to their true numerical values for calculations and display. - **BigNumber Handling**: Use [ethers.js BigNumber](https://docs.ethers.org/v5/api/utils/bignumber/) for all numeric operations to avoid precision issues. - **Error Handling**: Implement proper error handling for network issues and stale data. Remember that different MVR feeds may have different data structures. Always check the [SmartData Addresses](/data-feeds/smartdata/addresses) page for the exact format and decimals for the specific MVR feed you are using. --- # Using MVR Feeds on EVM Chains Source: https://docs.chain.link/data-feeds/mvr-feeds/guides/evm-solidity This guide explains how to use [Multiple-Variable Response (MVR) feeds](/data-feeds/mvr-feeds) data in your consumer contracts on EVM chains using Solidity. MVR feeds store multiple data points in a single byte array onchain. To consume this data in your contract: 1. **Obtain the proxy address and data structure**: - Find the `BundleAggregatorProxy` address for the specific MVR feed you want to read on the [SmartData Addresses](/data-feeds/smartdata/addresses?page=1#networks) page - Expand the "MVR Bundle Info" section to see the exact data structure, field types, and decimals - Note these details as you'll need to match this structure exactly in your code 2. **Call [`latestBundle()`](/data-feeds/mvr-feeds/api-reference#latestbundle)**: Retrieve the feed's latest onchain data as a bytes array. 3. **Check data staleness**: Use [`latestBundleTimestamp()`](/data-feeds/mvr-feeds/api-reference#latestbundletimestamp) to compare against current time and verify it hasn't exceeded your maximum acceptable staleness threshold. 4. **Decode the data**: Convert the bytes array into the known struct type (as documented for each feed). 5. **Apply decimals** (if needed): For numeric fields, scale the raw values by dividing by `10^decimals[i]` to get the true numerical values. 6. **Use in your dApp**: Store or process the decoded values as required by your application. ## Step-by-Step Example Below is a step-by-step explanation, followed by a full example contract that ties everything together. ### 1. Define a Data Structure > **NOTE: Data Structure** > > Find the exact data structure for your specific MVR feed on the [SmartData > Addresses](/data-feeds/smartdata/addresses?page=1#networks) page. Click on the feed entry and expand the "MVR Bundle > Info" section to see all fields, their types, and decimals. Each MVR feed publishes data in a specific layout. For example, imagine an investment feed that reports: ```solidity struct Data { uint256 netAssetValue; // e.g., 8 decimal places uint256 assetsUnderManagement; // e.g., 8 decimal places uint256 outstandingShares; // e.g., 2 decimal places uint256 netIncomeExpenses; // e.g., 2 decimal places bool openToNewInvestors; // boolean, no decimals } ``` Your consumer contract must replicate this structure in the **exact same order** and with the same data types to decode the feed data correctly. ### 2. Import the IBundleAggregatorProxy Interface Your contract will need to interact with the MVR feed's proxy contract. The [`IBundleAggregatorProxy`](/data-feeds/mvr-feeds/api-reference#ibundleaggregatorproxy) interface provides the necessary functions: - [`latestBundle()`](/data-feeds/mvr-feeds/api-reference#latestbundle): Retrieves the raw data bundle. - [`bundleDecimals()`](/data-feeds/mvr-feeds/api-reference#bundledecimals): Retrieves the decimal places for numeric fields. - [`latestBundleTimestamp()`](/data-feeds/mvr-feeds/api-reference#latestbundletimestamp): Retrieves the timestamp of the last update. Import it directly from the `@chainlink/contracts` library: ```solidity import {IBundleAggregatorProxy} from "@chainlink/contracts/src/v0.8/data-feeds/interfaces/IBundleAggregatorProxy.sol"; ``` You will then use this interface to create an instance of the proxy in your consumer contract. ### 3. Validate Data Staleness Before using the data, it's best practice to verify it has not become stale by checking the timestamp of the latest update against the current time. Stale data can lead to incorrect business decisions or vulnerabilities in your application. Staleness checking examples: **Option 1: Simple boolean check (recommended)** ```solidity function isDataFresh() public view returns (bool) { uint256 lastUpdateTime = s_proxy.latestBundleTimestamp(); return (block.timestamp - lastUpdateTime) <= STALENESS_THRESHOLD; } // In your main function: if (!isDataFresh()) { revert StaleData(lastUpdateTime, block.timestamp, STALENESS_THRESHOLD); } ``` **Option 2: Direct validation** ```solidity uint256 lastUpdateTime = s_proxy.latestBundleTimestamp(); require(block.timestamp - lastUpdateTime <= stalenessThreshold, "MVR feed data is stale"); ``` **Important**: Don't use arbitrary values for staleness thresholds. The appropriate threshold should be determined by: 1. Find the feed's **heartbeat interval** on the [MVR Feeds Addresses](/data-feeds/smartdata/addresses?page=1#networks) page (click "Show more details") 2. Set a threshold that aligns with this interval, usually the heartbeat plus a small buffer 3. Consider your specific use case requirements (some applications may need very fresh data) ### 4. Add Error Handling and Safety Checks For production contracts, include proper error handling with custom errors and safety checks: ```solidity // Custom errors for better debugging error StaleData(uint256 lastUpdateTimestamp, uint256 blockTimestamp, uint256 threshold); error InsufficientDecimals(uint256 expected, uint256 actual); // Check that the decimals array has enough elements before accessing by index if (decimals.length < 4) { revert InsufficientDecimals(4, decimals.length); } ``` **Helper functions for testing and debugging:** - `isDataFresh()`: Returns a simple true/false for data freshness (excellent for block explorer testing) - `getLatestBundleTimestamp()`: Returns the timestamp of the most recent update - `storeDecimals()`: Fetches and stores the decimals array for repeated use ### 5. Read and Decode the Feed Data Use `abi.decode` to convert the returned `bytes` array into your `Data` struct: ```solidity bytes memory rawBundle = s_proxy.latestBundle(); Data memory decodedData = abi.decode(rawBundle, (Data)); ``` This will populate each field in `decodedData` according to your struct definition. ### 6. Handle Decimals (if applicable) Similar to how ETH has 18 decimals, numeric fields in MVR feeds are typically stored with fixed-point precision. This means you need to divide raw values by a power of 10 to get their true numerical values for accurate calculations. The [`bundleDecimals()`](/data-feeds/mvr-feeds/api-reference#bundledecimals) function returns an array that tells you how many decimal places each field uses. Each index in this array corresponds to a field in your struct (in the same order). - **Raw Value**: What you get directly from the feed (e.g., `1850000000` for a value that represents `18.5`) - **Actual Value**: What the number actually represents mathematically after proper decimal scaling - **Conversion**: `actualValue = rawValue / (10 ^ decimals)` For example, if your MVR feed has the following fields and decimals: | Field | Raw Value | Decimals | Actual Value | | ----------------------- | ------------ | -------- | -------------------- | | `netAssetValue` | `1850000000` | 8 | `18.5` | | `assetsUnderManagement` | `5000000000` | 8 | `50.0` | | `outstandingShares` | `12500` | 2 | `125.0` | | `netIncomeExpenses` | `7540` | 2 | `75.4` | | `openToNewInvestors` | `true` | 0 | `true` (no decimals) | Then [`bundleDecimals()`](/data-feeds/mvr-feeds/api-reference#bundledecimals) would return an array like `[8, 8, 2, 2, 0]`. In your code, you would convert the values like this: ```solidity // For netAssetValue with 8 decimals uint256 scaledValue = rawValue / 10**8; // 1850000000 / 10^8 = 18 (decimals truncated) ``` **Important**: - In Solidity, division with `uint256` results in integer division with truncation of any fractional part. If your application needs to maintain decimal precision, store the raw values and perform decimal conversion in your frontend application. - Always confirm the actual decimal configuration from the feed's documentation (see [MVR Feeds Addresses](/data-feeds/smartdata/addresses?page=1#networks)). Different feeds may use different decimal patterns or skip them entirely for non-numeric fields. ### 7. Store or Use the Decoded Values Finally, you can store the decoded values in your contract state or process them right away. ## Full Example: Consumer Contract Below is a full example that demonstrates how to: - Initialize the proxy in the constructor - Validate data staleness - Retrieve and decode the latest bundle - Adjust numeric fields by the correct decimal factor - Store both the raw onchain values and the scaled values > **NOTE: Adapt the Data Structure** > > The example below uses a generic Data struct for demonstration. You **must** modify this structure to match the exact fields, types, and order of your specific MVR feed: > > 1. Navigate to the [SmartData Addresses](/data-feeds/smartdata/addresses?page=1#networks) page > 2. Find and click on your specific MVR feed > 3. Expand the "MVR Bundle Info" section to see all fields and their types > 4. Create your Data struct with exactly the same fields, types, and order > 5. Update the field names in `consumeData()` to match your struct ```sol // SPDX-License-Identifier: MIT pragma solidity 0.8.24; import {IBundleAggregatorProxy} from "@chainlink/contracts/src/v0.8/data-feeds/interfaces/IBundleAggregatorProxy.sol"; /** * @notice This struct defines the exact data structure of the MVR feed * @dev The order and types must match exactly what's defined in the feed */ struct Data { uint256 netAssetValue; uint256 assetsUnderManagement; uint256 outstandingShares; uint256 netIncomeExpenses; bool openToNewInvestors; } contract MVRDataConsumer { // Reference to the MVR feed proxy IBundleAggregatorProxy public s_proxy; // Maximum allowed staleness duration for the data // IMPORTANT: This should be configured based on the specific feed's heartbeat interval // Check the feed's documentation for the appropriate value instead of using this example value uint256 public immutable STALENESS_THRESHOLD; // Storage for scaled values (after dividing by decimals) uint256 public netAssetValue; uint256 public assetsUnderManagement; uint256 public outstandingShares; uint256 public netIncomeExpenses; bool public openToNewInvestors; // Storage for original onchain values (no decimal adjustments) uint256 public rawNetAssetValue; uint256 public rawAssetsUnderManagement; uint256 public rawOutstandingShares; uint256 public rawNetIncomeExpenses; // Keep track of decimals for each field in the struct. // Non-numeric fields (e.g., bool) typically return 0. uint8[] public decimals; // Error for stale data error StaleData(uint256 lastUpdateTimestamp, uint256 blockTimestamp, uint256 threshold); // Error for insufficient decimals array error InsufficientDecimals(uint256 expected, uint256 actual); /** * @notice Constructor that sets the staleness threshold for the feed * @param _proxy The address of the MVR feed's proxy contract * @param _stalenessThreshold Maximum time (in seconds) since last update before data is considered stale * @dev The threshold should be based on the feed's heartbeat interval from documentation * For example, if a feed updates every 24 hours (86400s), you might set this to 86400s + some buffer */ constructor( IBundleAggregatorProxy _proxy, uint256 _stalenessThreshold ) { s_proxy = _proxy; STALENESS_THRESHOLD = _stalenessThreshold; } /** * @notice Stores the decimals array in your contract for repeated usage. * @dev Index mapping for this example: * 0 -> netAssetValue, * 1 -> assetsUnderManagement, * 2 -> outstandingShares, * 3 -> netIncomeExpenses, * 4 -> openToNewInvestors (likely returns 0). */ function storeDecimals() external { decimals = s_proxy.bundleDecimals(); } /** * @notice Returns the timestamp of the most recent MVR feed update. */ function getLatestBundleTimestamp() external view returns (uint256) { return s_proxy.latestBundleTimestamp(); } /** * @notice Simple boolean check for data freshness (block explorer friendly) * @return true if data is fresh, false if stale */ function isDataFresh() public view returns (bool) { uint256 lastUpdateTime = s_proxy.latestBundleTimestamp(); return (block.timestamp - lastUpdateTime) <= STALENESS_THRESHOLD; } /** * @notice Fetches and decodes the latest MVR feed data, then stores both the raw and scaled values. * @dev This process demonstrates the complete flow of consuming MVR feed data: * 1. Check data freshness * 2. Fetch the raw bytes * 3. Decode into the struct matching the feed's data structure * 4. Store raw values (preserving original precision) * 5. Apply decimal conversions to get the true numerical values */ function consumeData() external { // Check data freshness before proceeding if (!isDataFresh()) { uint256 lastUpdateTime = s_proxy.latestBundleTimestamp(); revert StaleData(lastUpdateTime, block.timestamp, STALENESS_THRESHOLD); } // Ensure we have the decimals array - if not, fetch it if (decimals.length == 0) { decimals = s_proxy.bundleDecimals(); } // Verify we have enough decimal values for our struct fields if (decimals.length < 4) { revert InsufficientDecimals(4, decimals.length); } // 1. Retrieve the raw bytes from the MVR feed // This is the encoded form of all data fields packed together bytes memory b = s_proxy.latestBundle(); // 2. Decode the raw bytes into our known struct // The struct Data must match exactly what the feed encodes Data memory d = abi.decode(b, (Data)); // 3. Store the raw (original onchain) values // These preserve the full precision as reported by the feed rawNetAssetValue = d.netAssetValue; rawAssetsUnderManagement = d.assetsUnderManagement; rawOutstandingShares = d.outstandingShares; rawNetIncomeExpenses = d.netIncomeExpenses; openToNewInvestors = d.openToNewInvestors; // Boolean, no need for decimal adjustment // 4. Convert values by dividing by 10^decimals[i] // This removes the decimal scaling factor to get the human-readable representation // Note: This uses integer division which truncates decimal places // For example, if decimals[0] = 8 and rawNetAssetValue = 1850000000, // then netAssetValue = 18 (integer division, decimals are truncated) netAssetValue = d.netAssetValue / (10 ** decimals[0]); assetsUnderManagement = d.assetsUnderManagement / (10 ** decimals[1]); outstandingShares = d.outstandingShares / (10 ** decimals[2]); netIncomeExpenses = d.netIncomeExpenses / (10 ** decimals[3]); // Note: We don't need to apply decimals to boolean fields // The openToNewInvestors field typically has 0 decimals in the array } } ``` **Key Points**: - **Exact Order Matters**: The struct fields and their types must match the feed's definition. - **Decimals**: Use [`bundleDecimals()`](/data-feeds/mvr-feeds/api-reference#bundledecimals) to learn how to scale the numeric fields. Non-numeric fields (e.g., bool) do not need scaling. - **Timestamps**: [`latestBundleTimestamp()`](/data-feeds/mvr-feeds/api-reference#latestbundletimestamp) returns the block timestamp of the last report and should be used to validate data staleness. - **Staleness Threshold**: Set appropriate staleness thresholds based on each feed's documented heartbeat interval, not arbitrary values. - **Error Handling**: Use custom errors (`StaleData`, `InsufficientDecimals`) for better debugging and include safety checks for array bounds. - **Testing Functions**: Implement helper functions like `isDataFresh()` for easy testing and debugging. - **No Historical Data**: MVR feeds typically only store the latest data onchain. If you need historical data, you must capture it in your own contract or via an offchain indexer. --- # Multiple-Variable Response (MVR) Feeds Guides Source: https://docs.chain.link/data-feeds/mvr-feeds/guides [Multiple-Variable Response (MVR) feeds](/data-feeds/mvr-feeds) allow smart contracts and offchain applications to retrieve multiple data points in a single transaction or call. Learn how to integrate MVR feeds into your applications using different tools and languages: - [Using MVR Feeds on EVM Chains (Solidity)](/data-feeds/mvr-feeds/guides/evm-solidity): Integrate MVR feeds directly into your smart contracts using Solidity. - [Using MVR Feeds with ethers.js (JS)](/data-feeds/mvr-feeds/guides/ethersjs): Access MVR feed data from your JavaScript applications using the ethers.js library. - [Using MVR Feeds with Viem (TS)](/data-feeds/mvr-feeds/guides/viem): Access MVR feed data from your TypeScript applications using the Viem library. --- # Using MVR Feeds with Viem (TS) Source: https://docs.chain.link/data-feeds/mvr-feeds/guides/viem This guide explains how to use [Multiple-Variable Response (MVR) feeds](/data-feeds/mvr-feeds) in your TypeScript applications using the [viem](https://viem.sh/) library. MVR feeds store multiple data points in a single byte array onchain. To consume this data in your TypeScript application: 1. **Obtain the proxy address and data structure**: - Find the `BundleAggregatorProxy` address for the specific MVR feed you want to read on the [SmartData Addresses](/data-feeds/smartdata/addresses?page=1#networks) page - Expand the "MVR Bundle Info" section to see the exact data structure, field types, and decimals - Note these details as you'll need to match this structure exactly in your TypeScript interfaces 2. **Set up viem**: Create a client and contract instance to interact with the feed. 3. **Check data staleness**: Compare the feed's latest timestamp against current time to verify it hasn't exceeded your maximum acceptable staleness threshold. 4. **Fetch and decode the data**: Retrieve the feed's latest bundle and decode the bytes array. 5. **Apply decimals**: Scale numeric values to their true decimal representation for accurate calculations and display. 6. **Use in your application**: Process or display the decoded values. ## Prerequisites - [Node.js](https://nodejs.org/en/download/) environment (Node.js >=16.x recommended) - [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) or [yarn](https://yarnpkg.com/getting-started) installed - [viem](https://viem.sh/) library installed: ```shell npm install viem ``` or ```shell yarn add viem ``` - For TypeScript projects that use Node.js environment variables (`process.env`): ```shell npm install --save-dev @types/node ``` or ```shell yarn add --dev @types/node ``` - An RPC URL for the network where the MVR feed is deployed. You can sign up for a personal endpoint from [Alchemy](https://www.alchemy.com/) or [Infura](https://infura.io/). - Set up environment variables: ```shell npm install dotenv ``` or ```shell yarn add dotenv ``` ## Step-by-Step Implementation ### 1. Define the BundleAggregatorProxy ABI First, define the ABI for the `BundleAggregatorProxy` contract: ```typescript import { createPublicClient, http, parseAbiItem, type PublicClient } from "viem" import { mainnet } from "viem/chains" // Import your target chain // Define the ABI for the BundleAggregatorProxy const bundleAggregatorProxyABI = [ parseAbiItem("function latestBundle() external view returns (bytes)"), parseAbiItem("function bundleDecimals() external view returns (uint8[])"), parseAbiItem("function latestBundleTimestamp() external view returns (uint256)"), ] as const ``` ### 2. Set Up the Client and Contract > **NOTE: Viem Supported Chains** > > For a complete list of supported chains in Viem, refer to the [Viem Chains > Reference](https://github.com/wevm/viem/blob/main/src/chains/index.ts) on GitHub. Connect to a blockchain provider and create the contract instance: ```typescript // Load environment variables (in Node.js) import "dotenv/config" // Get RPC URL from environment variables const rpcUrl = process.env.RPC_URL if (!rpcUrl) { throw new Error("RPC_URL not found in environment variables") } // Connect to a client securely const client = createPublicClient({ chain: mainnet, // Replace with your target chain transport: http(rpcUrl), }) // MVR Feed proxy address - replace with the actual address for your feed const proxyAddress = (process.env.MVR_FEED_ADDRESS as `0x${string}`) || ("0x..." as `0x${string}`) ``` Create a `.env` file in your project root (and add it to `.gitignore`): ``` # .env RPC_URL= MVR_FEED_ADDRESS= ``` ### 3. Define Type-Safe Data Structures > **NOTE: MVR Feed-Specific Data Structure** > > Find the exact data structure for your specific MVR feed on the [SmartData > Addresses](/data-feeds/smartdata/addresses?page=1#networks) page. Click on the feed entry and expand the "MVR Bundle > Info" section to see all fields, their types, and decimals. Define a TypeScript interface that corresponds to your feed's data structure: ```typescript import { createPublicClient, http, parseAbiItem, formatUnits, decodeAbiParameters, type PublicClient } from "viem" import { mainnet } from "viem/chains" import "dotenv/config" // Interface for formatted data with appropriate types interface FormattedResult { [key: string]: | { raw?: bigint formatted: string decimals?: number } | boolean } ``` ### 4. Create Functions to Check Staleness Before using the data, check its timestamp to ensure it's not stale: ```typescript async function checkDataStaleness( client: PublicClient, proxyAddress: `0x${string}`, stalenessThreshold: number = 86400 // 24 hours in seconds by default ): Promise { // Get the latest timestamp const lastUpdateTime = (await client.readContract({ address: proxyAddress, abi: bundleAggregatorProxyABI, functionName: "latestBundleTimestamp", })) as bigint const timestamp = Number(lastUpdateTime) const now = Math.floor(Date.now() / 1000) if (now - timestamp > stalenessThreshold) { throw new Error(`Data is stale. Last update was ${now - timestamp} seconds ago.`) } return true } ``` **Important**: Don't use arbitrary values for staleness thresholds. The appropriate threshold should be determined by: 1. Find the feed's **heartbeat interval** on the [SmartData Addresses](/data-feeds/smartdata/addresses?page=1#networks) page (click "Show more details") 2. Set a threshold that aligns with this interval, usually the heartbeat plus a small buffer 3. Consider your specific use case requirements ### 5. Fetch and Decode the Bundle Data > **NOTE: MVR Feed-Specific Data Structure** > > Find the exact data structure for your specific MVR feed on the [SmartData > Addresses](/data-feeds/smartdata/addresses?page=1#networks) page. Click on the feed entry and expand the "MVR Bundle > Info" section to see all fields, their types, and decimals. ```typescript // Define types array for decoding // This MUST match the feed's exact structure const dataTypes = ["uint256", "uint256", "uint256", "uint256", "bool"] as const // TypeScript type for the decoded tuple type DecodedData = [bigint, bigint, bigint, bigint, boolean] const fieldNames = [ "netAssetValue", "assetsUnderManagement", "outstandingShares", "netIncomeExpenses", "openToNewInvestors", ] as const async function getRawData( client: PublicClient, proxyAddress: `0x${string}` ): Promise> { // Check data staleness first await checkDataStaleness(client, proxyAddress) try { // Get raw bundle data const bundleBytes = (await client.readContract({ address: proxyAddress, abi: bundleAggregatorProxyABI, functionName: "latestBundle", })) as `0x${string}` // Define parameter structure for decoding // This describes the data types we expect to decode from the bytes const parameterStructure = dataTypes.map((type) => ({ type })) // Decode the bytes into an array of values const decodedValues = decodeAbiParameters(parameterStructure, bundleBytes) // Map array values to named fields for easier access const result: Record = {} fieldNames.forEach((name, index) => { if (index < decodedValues.length) { result[name] = decodedValues[index] } }) return result } catch (error) { console.error("Error fetching or decoding data:", error) throw error } } ``` ### 6. Apply Decimal Scaling Factors > **NOTE: Handling Decimals in TypeScript** > > Viem uses bigint for all numeric values to safely handle the large numbers common in blockchain applications. When > formatting these values for display or calculations, we need to apply the appropriate decimal scaling. ```typescript import { formatUnits } from "viem" async function getFormattedData(client: PublicClient, proxyAddress: `0x${string}`): Promise { // Get raw data first const rawData = await getRawData(client, proxyAddress) // Get decimals for each field const decimalsArray = (await client.readContract({ address: proxyAddress, abi: bundleAggregatorProxyABI, functionName: "bundleDecimals", })) as readonly number[] // Format the data with appropriate decimal scaling const result: FormattedResult = {} fieldNames.forEach((name, index) => { const value = rawData[name] if (typeof value === "boolean") { result[name] = value } else if (typeof value === "bigint") { const decimalPlaces = index < decimalsArray.length ? decimalsArray[index] : 0 result[name] = { raw: value, decimals: decimalPlaces, formatted: formatUnits(value, decimalPlaces), } } }) return result } ``` ## Complete Example Here's a complete example that ties everything together into a reusable class: ```typescript import { createPublicClient, http, parseAbiItem, formatUnits, decodeAbiParameters, type PublicClient } from "viem" import { mainnet } from "viem/chains" import "dotenv/config" // A more generic return type for numeric fields interface FormattedNumericValue { raw: bigint formatted: string decimals: number } // A more specific return type for formatted data interface FormattedResult { [key: string]: FormattedNumericValue | boolean } class MVRFeedClient { private readonly client: PublicClient private readonly proxyAddress: `0x${string}` private readonly stalenessThreshold: number private readonly dataTypes: readonly string[] private readonly fieldNames: readonly string[] private readonly abi = [ parseAbiItem("function latestBundle() external view returns (bytes)"), parseAbiItem("function bundleDecimals() external view returns (uint8[])"), parseAbiItem("function latestBundleTimestamp() external view returns (uint256)"), ] as const /** * Creates a new MVR Feed client * @param proxyAddress - The address of the BundleAggregatorProxy contract * @param client - A viem PublicClient * @param config - Configuration options */ constructor( proxyAddress: `0x${string}`, client: PublicClient, config: { stalenessThreshold?: number dataTypes?: readonly string[] fieldNames?: readonly string[] } = {} ) { this.client = client this.proxyAddress = proxyAddress this.stalenessThreshold = config.stalenessThreshold ?? 86400 // 24 hours default // Configure data structure this.dataTypes = config.dataTypes ?? [ "uint256", // netAssetValue "uint256", // assetsUnderManagement "uint256", // outstandingShares "uint256", // netIncomeExpenses "bool", // openToNewInvestors ] this.fieldNames = config.fieldNames ?? [ "netAssetValue", "assetsUnderManagement", "outstandingShares", "netIncomeExpenses", "openToNewInvestors", ] } /** * Checks if the data hasn't exceeded the staleness threshold based on the timestamp * @returns Promise True if data is not stale * @throws Error If data is stale */ async checkDataStaleness(): Promise { const timestamp = Number( await this.client.readContract({ address: this.proxyAddress, abi: this.abi, functionName: "latestBundleTimestamp", }) ) const now = Math.floor(Date.now() / 1000) if (now - timestamp > this.stalenessThreshold) { throw new Error(`Data is stale. Last update: ${new Date(timestamp * 1000).toISOString()}`) } return true } /** * Gets the raw decoded data from the feed * @returns Promise> The decoded data */ async getRawData(): Promise> { await this.checkDataStaleness() try { const bundleBytes = (await this.client.readContract({ address: this.proxyAddress, abi: this.abi, functionName: "latestBundle", })) as `0x${string}` // Define parameter structure for decoding const parameterStructure = this.dataTypes.map((type) => ({ type })) // Decode the bytes into an array of values const decodedValues = decodeAbiParameters(parameterStructure, bundleBytes) as (bigint | boolean)[] // Map array values to named fields for easier access const result: Record = {} this.fieldNames.forEach((name, index) => { if (index < decodedValues.length) { result[name] = decodedValues[index] } }) return result } catch (error) { console.error("Error fetching or decoding data:", error) throw error } } /** * Gets formatted data with decimal adjustments for accurate numerical representation * @returns Promise The data with correct decimal scaling applied */ async getFormattedData(): Promise { const rawData = await this.getRawData() const decimalsArray = (await this.client.readContract({ address: this.proxyAddress, abi: this.abi, functionName: "bundleDecimals", })) as readonly number[] const result: FormattedResult = {} this.fieldNames.forEach((name, index) => { const value = rawData[name] if (typeof value === "boolean") { result[name] = value } else if (typeof value === "bigint") { const decimalPlaces = index < decimalsArray.length ? decimalsArray[index] : 0 result[name] = { raw: value, decimals: decimalPlaces, formatted: formatUnits(value, decimalPlaces), } } }) return result } } // Example usage async function main() { // Get configuration from environment variables for security const rpcUrl = process.env.RPC_URL if (!rpcUrl) { throw new Error("RPC_URL environment variable not set") } const feedAddress = process.env.MVR_FEED_ADDRESS as `0x${string}` if (!feedAddress) { throw new Error("MVR_FEED_ADDRESS environment variable not set") } // Create client const client = createPublicClient({ chain: mainnet, // Replace with your target chain, do not forget to update the import transport: http(rpcUrl), }) // Create MVR feed client const mvrClient = new MVRFeedClient(feedAddress, client, { // IMPORTANT: Update these to match your specific feed's structure dataTypes: ["uint256", "uint256", "uint256", "uint256", "bool"], fieldNames: [ "netAssetValue", "assetsUnderManagement", "outstandingShares", "netIncomeExpenses", "openToNewInvestors", ], }) try { const data = await mvrClient.getFormattedData() console.log("MVR Feed Data:") // Display the data for (const [key, value] of Object.entries(data)) { if (typeof value === "boolean") { console.log(`${key}: ${value}`) } else { console.log(`${key}: ${value.formatted} (raw: ${value.raw.toString()})`) } } } catch (error) { console.error("Error fetching MVR data:", error) } } main() ``` ### Customizing and Running the Example To run the complete example above: 1. Find the MVR feed you want to read on the [SmartData Addresses](/data-feeds/smartdata/addresses) page - Copy the `BundleAggregatorProxy` address for the next step - Expand the "MVR Bundle Info" section to see all fields, their types, and decimals 2. Create a `.env` file in your project directory: ``` RPC_URL= MVR_FEED_ADDRESS= ``` 3. Update the code to match your specific MVR feed: ```typescript // IMPORTANT: Update these to match your specific feed's structure const mvrClient = new MVRFeedClient(feedAddress, client, { dataTypes: ["uint256", "uint256", "uint256", "uint256", "bool"], fieldNames: [ "netAssetValue", "assetsUnderManagement", "outstandingShares", "netIncomeExpenses", "openToNewInvestors", ], }) ``` 4. Install the required dependencies: ```shell npm install viem dotenv ``` 5. Run the application: ```shell ts-node your-script-filename.ts ``` or ```shell npx tsx your-script-filename.ts ``` The output should display the formatted data from the MVR feed with both formatted values and raw values. ## Key Points - **ABI Definition**: Use viem's `parseAbiItem` for type-safe ABI definitions. - **Data Structure**: The data types must exactly match the feed's format. - **Staleness Check**: Always verify data staleness using the timestamp. - **TypeScript Types**: Leverage TypeScript to create type-safe interfaces for your data. - **Decimal Scaling**: Use viem's `formatUnits` to convert bigint values to properly formatted strings. - **Error Handling**: Implement proper error handling for network issues and stale data. Remember that different MVR feeds may have different data structures. Always check the [SmartData Addresses](/data-feeds/smartdata/addresses) page for the exact format and decimals for the specific MVR feed you are using. --- # Multiple-Variable Response (MVR) Feeds Source: https://docs.chain.link/data-feeds/mvr-feeds **Multiple-Variable Response (MVR) feeds** bundle multiple data points into a single onchain update, offering more versatility compared to feeds that store only one numeric value. Traditional Chainlink Data Feeds typically provide a single numeric value such as a price. By contrast, MVR feeds can return multiple values of different types in a single update, including both numeric and non-numeric data. ## Key Benefits 1. **Multiple Data Points**: Package related data together in one feed. For example, an investment fund feed might include: | Field | Type | | ----------------------- | --------- | | `netAssetValue` | `uint256` | | `assetsUnderManagement` | `uint256` | | `outstandingShares` | `uint256` | | `netIncomeExpenses` | `uint256` | | `openToNewInvestors` | `bool` | 2. **Extensibility**: The feed structure can be extended or updated without deploying a new contract. 3. **Efficiency**: Multiple data points update in a single transaction. 4. **Flexible Types**: Support for various data types beyond just numbers. ## Reading MVR Feed Data MVR feeds use the `BundleAggregatorProxy` contract as their interface, which provides three key functions: - [`latestBundle()`](/data-feeds/mvr-feeds/api-reference#latestbundle): Returns the complete data bundle as a bytes array - [`bundleDecimals()`](/data-feeds/mvr-feeds/api-reference#bundledecimals): Returns the decimal places for each numeric field - [`latestBundleTimestamp()`](/data-feeds/mvr-feeds/api-reference#latestbundletimestamp): Returns when the data was last updated To use these feeds in your smart contracts, you'll need to: 1. Obtain the `BundleAggregatorProxy` address for your desired feed from the [SmartData addresses](/data-feeds/smartdata/addresses) page (use the *Show Multiple-Variable Response (MVR) feeds* checkbox to filter the list) 2. Retrieve the data using `latestBundle()` 3. Decode the bytes array into the appropriate structure **Note**: MVR feeds only store the most recent data onchain. If you need historical data, you must capture it within your own contract or through an offchain indexer. Check our [implementation guides](/data-feeds/mvr-feeds/guides) for detailed instructions on integrating MVR feeds using Solidity, ethers.js, or Viem. ## Resources - [SmartData Addresses](/data-feeds/smartdata/addresses?page=1#networks): Find `BundleAggregatorProxy` addresses and data structures for available MVR feeds. - [MVR Feeds API Reference](/data-feeds/mvr-feeds/api-reference): Complete interface definitions and function signatures. - Implementation Guides: - [Solidity Guide](/data-feeds/mvr-feeds/guides/evm-solidity): For smart contract developers using Solidity - [ethers.js Guide](/data-feeds/mvr-feeds/guides/ethersjs): For frontend or Node.js developers using ethers.js v5 - [Viem Guide](/data-feeds/mvr-feeds/guides/viem): For TypeScript developers using the Viem library --- # What are Chainlink Data Feeds? Source: https://docs.chain.link/data-feeds/overview > **NOTE: Talk to an expert** > > [Contact us](https://chain.link/contact?ref_id=pricefeeds) to talk to an expert about integrating Chainlink > Data Feeds with your applications. Chainlink Data Feeds are the quickest way to connect your smart contracts to real-world data such as asset prices, reserve balances, and L2 sequencer health. If you already started a project and need to integrate Chainlink, you can [add Chainlink to your existing project](/resources/create-a-chainlinked-project?parent=dataFeeds#installing-into-existing-projects) with the [`@chainlink/contracts` NPM package](https://www.npmjs.com/package/@chainlink/contracts). ## Types of data feeds Data feeds provide many different types of data for your applications. - [Price Feeds](#price-feeds) - [SmartData Feeds](#smartdata-feeds) - [Multiple-Variable Response (MVR) Feeds](#multiple-variable-response-mvr-feeds) - [Smart Value Recapture (SVR) Feeds](#smart-value-recapture-svr-feeds) - [Rate and Volatility Feeds](#rate-and-volatility-feeds) - [L2 sequencer uptime feeds](#l2-sequencer-uptime-feeds) - [Self-managed feeds](#self-managed-feeds) ### Price Feeds Smart contracts often act in real-time on data such as prices of assets. This is especially true in [DeFi](https://chain.link/use-cases/defi). For example, lending and borrowing platforms like [Aave](https://aave.com) utilize Data Feeds to assess collateral value accurately, while perpetual platforms such as [GMX](https://gmx.io) rely on these feeds to validate offchain data and ensure that each deposit or withdrawal executes at the correct market value. Data Feeds aggregate many data sources and publish them onchain using a combination of the [Decentralized Data Model](/architecture-overview/architecture-decentralized-model?parent=dataFeeds) and [Offchain Reporting](/architecture-overview/off-chain-reporting?parent=dataFeeds). - To learn how to use Price Feeds, see the [Price Feeds](/data-feeds/price-feeds) documentation. - See the [Data Feeds Contract Addresses](/data-feeds/price-feeds/addresses) page for a list of available networks and addresses. ### SmartData Feeds Chainlink SmartData is a suite of onchain data offerings designed to unlock the utility, accessibility, and reliability of tokenized real-world assets (RWAs). By providing secure minting assurances alongside essential real-world data such as reserves, Net Asset Value (NAV), and Assets Under Management (AUM) data, the SmartData suite embeds security and enriches data into tokenized RWA offerings. - To learn more about the broader SmartData suite, see the [SmartData](/data-feeds/smartdata) documentation. - See the [SmartData Contract Addresses](/data-feeds/smartdata/addresses) page for a list of available networks and addresses. #### Multiple-Variable Response (MVR) Feeds The SmartData suite includes [Multiple-Variable Response (MVR) Feeds](/data-feeds/mvr-feeds), which bundle multiple data points of various types into a single onchain update. Unlike traditional feeds that provide only one numeric value, MVR feeds can return multiple values of different types (both numeric and non-numeric) in a single transaction. - To learn more about MVR Feeds, see the [Multiple-Variable Response (MVR) Feeds](/data-feeds/mvr-feeds) documentation. - See the [SmartData Contract Addresses](/data-feeds/smartdata/addresses) page for a list of available networks and addresses. Use the *Show Multiple-Variable Response (MVR) feeds* checkbox to filter the list. ### Smart Value Recapture (SVR) Feeds Smart Value Recapture (SVR) Feeds introduce a mechanism to recapture Oracle Extractable Value (OEV), a form of MEV associated with oracle updates, often observed during liquidations. - To learn more, see the [Smart Value Recapture (SVR) Feeds](/data-feeds/svr-feeds) documentation. - Find SVR-enabled addresses on the [Price Feed Addresses](/data-feeds/price-feeds/addresses) page by filtering for SVR feeds. ### Rate and Volatility Feeds Several feeds provide interest rate curve data, APY data, and realized asset price volatility. - To learn more, see the [Rate and Volatility Feeds](/data-feeds/rates-feeds) documentation. - See the [Rate and Volatility Contract Addresses](/data-feeds/rates-feeds/addresses) page for a list of available networks and addresses. ### L2 sequencer uptime feeds L2 sequencer feeds track the last known status of the sequencer on an L2 network at a given point in time. This helps you prevent mass liquidations by providing a grace period to allow customers to react to these events. To learn how to use L2 sequencer uptime feeds, see the [L2 Sequencer Uptime Feeds](/data-feeds/l2-sequencer-feeds) documentation. ### Self-managed feeds Some networks publish Data Feed–style proxy contracts where **onchain updates are written by the chain or a third-party operator** (for example using a CRE workflow that reads [Chainlink Data Streams](/data-streams)), rather than through Chainlink Labs–managed decentralized oracle networks. **Chainlink Labs does not operate, monitor, or list addresses** for these deployments in official feed catalogs. For responsibilities, monitoring scope, and how to obtain contract addresses, see [Self-Managed Feeds](/data-feeds/self-managed-feeds). ## Components of a data feed Data Feeds are examples of decentralized oracle networks with different interfaces depending on the type of data they provide. Each feed type includes specific components: ### Single-value Feed Components Data feeds that deliver a single numeric value (such as price feeds or single-value [SmartData feeds](/data-feeds/smartdata)) include the following components: - **Consumer**: A consumer is an onchain or offchain application that uses Data Feeds. Consumer contracts use the [`AggregatorV3Interface`](https://github.com/smartcontractkit/chainlink/blob/contracts-v1.3.0/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol) to call functions on the proxy contract and retrieve information from the aggregator contract. For a complete list of functions available in the `AggregatorV3Interface`, see the [Data Feeds API Reference](/data-feeds/api-reference/#aggregatorv3interface). - **Proxy contract**: Proxy contracts are onchain proxies that point to the aggregator for a particular data feed. Using proxies enables the underlying aggregator to be upgraded without any service interruption to consuming contracts. Proxy contracts can vary from one data feed to another, but the [`EACAggregatorProxy.sol` contract](https://github.com/smartcontractkit/chainlink/blob/contracts-v1.0.0/contracts/src/v0.6/EACAggregatorProxy.sol) on Github is a common example. - **Aggregator contract**: An aggregator is a contract that receives periodic data updates from the oracle network. Aggregators store aggregated data onchain so that consumers can retrieve it and act upon it within the same transaction. For a complete list of functions and variables available on most aggregator contracts, see the [Data Feeds API Reference](/data-feeds/api-reference/#accesscontrolledoffchainaggregator). To learn how to create a consumer contract that uses an existing price feed, read the [Using Data Feeds](/data-feeds/price-feeds) documentation. ### MVR Feed Components [Multiple-Variable Response (MVR) Feeds](/data-feeds/mvr-feeds), which deliver multiple data points of various types in a single bundle, include the following components: - **Consumer**: A consumer is an onchain or offchain application that uses MVR Feeds. Consumer contracts use the [`IBundleAggregatorProxy`](/data-feeds/mvr-feeds/api-reference/#ibundleaggregatorproxy) interface to call functions on the proxy contract and retrieve bundle data. - **Bundle Aggregator Proxy**: This proxy contract points to the underlying bundle aggregator. Using proxies enables the underlying aggregator to be upgraded without service interruption to consuming contracts. The proxy exposes functions like [`latestBundle()`](/data-feeds/mvr-feeds/api-reference/#latestbundle), [`bundleDecimals()`](/data-feeds/mvr-feeds/api-reference/#bundledecimals), and [`latestBundleTimestamp()`](/data-feeds/mvr-feeds/api-reference/#latestbundletimestamp) for consumers to access the bundled data. To learn how to create a consumer contract that uses an existing MVR feed, read the [Using MVR Feeds on EVM Chains (Solidity)](/data-feeds/mvr-feeds/guides/evm-solidity) guide. ## Reading proxy and aggregator configurations Because the proxy and aggregator contracts are all onchain, you can see the current configuration by reading the variables through an [ABI](https://docs.soliditylang.org/en/latest/abi-spec.html) or using a blockchain explorer for your network. For example, you can see the [BTC/USD proxy configuration](https://etherscan.io/address/0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c#readContract) on the Ethereum network using Etherscan. If you read the BTC/USD proxy configuration, you can query all of the functions and variables that are publicly accessible for that contract including the `aggregator` address, `latestRoundData()` function, `latestAnswer` variable, `owner` address, `latestTimestamp` variable, and several others. To see descriptions for the proxy contract variables and functions, see the source code for your specific data feed on [Etherscan](https://etherscan.io/address/0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c#code#L568). The proxy contract points to an aggregator. This allows you to retrieve data through the proxy even if the aggregator is upgraded. If you view the `aggregator` address defined in the proxy configuration, you can see the aggregator and its configuration. For example, see the [BTC/USD aggregator contract](https://etherscan.io/address/0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c#code) in Etherscan. This contract includes several variables and functions, including another `latestRoundData()`. To see descriptions for the aggregator variables and functions, see the source code on [GitHub](https://github.com/smartcontractkit/libocr/blob/master/contract/AccessControlledOffchainAggregator.sol) or [Etherscan](https://etherscan.io/address/0xAe74faA92cB67A95ebCAB07358bC222e33A34dA7#code#F1#L1). You can call the `latestRoundData()` function directly on the aggregator, but it is a best practice to use the proxy instead so that changes to the aggregator do not affect your application. Similar to the proxy contract, the aggregator contract has a `latestAnswer` variable, `owner` address, `latestTimestamp` variable, and several others. ## Components of an aggregator The aggregator contract has several variables and functions that might be useful for your application. Although aggregator contracts are similar for each data feed, some aggregators have different variables. Use the `typeAndVersion()` function on the aggregator to identify what type of aggregator it is and what version it is running. Always check the contract source code and configuration to understand how specific data feeds operate. For example, the [aggregator contract for BTC/USD on Arbitrum](https://arbiscan.io/address/0x942d00008D658dbB40745BBEc89A93c253f9B882#code) is different from the aggregators on other networks. For examples of the contracts that are typically used in aggregator deployments, see the [libocr repository](https://github.com/smartcontractkit/libocr/blob/master/contract/) on GitHub. For a complete list of functions and variables available on most aggregator contracts, see the [Data Feeds API Reference](/data-feeds/api-reference/#accesscontrolledoffchainaggregator). ## Updates to proxy and aggregator contracts To accommodate the dynamic nature of offchain environments, Chainlink Data Feeds are updated from time to time to add new features and capabilities as well as respond to externalities such as token migrations, protocol rebrands, extreme market events, and upstream issues with data or node operations. These updates include changes to the aggregator configuration or a complete replacement of the aggregator that the proxy uses. If you consume data feeds through the proxy, your applications can continue to operate during these changes. Proxy and aggregator contracts all have an `owner` address that has permission to change variables and functions. For example, if you read the [BTC/USD proxy contract](https://etherscan.io/address/0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c#readContract) in Etherscan, you can see the `owner` address. This address is a [multi-signature safe](https://docs.safe.global/getting-started/readme) (multisig) that you can also inspect. If you [view the multisig contract](https://etherscan.io/address/0x21f73D42Eb58Ba49dDB685dc29D3bF5c0f0373CA#readProxyContract) in Etherscan using the *Read as Proxy* feature, you can see the full details of the multisig including the list of addresses that can sign and the number of signers required for the multisig to approve actions on any contracts that it owns. The multisig-coordinated upgradability of Chainlink Data Feeds involves time-tested processes that balance collusion-resistance with the flexibility required to implement improvements and swiftly react to external conditions. The approach taken to upgradability will continue to evolve over time to meet user requirements. ## Monitoring data feeds When you build applications and protocols that depend on data feeds, include monitoring and safeguards to protect against the negative impact of extreme market events, possible malicious activity on third-party venues or contracts, potential delays, and outages. Create your own monitoring alerts based on deviations in the answers that data feeds provide. This will notify you when potential issues occur so you can respond to them. ### Check the latest answer against reasonable limits The data feed aggregator includes both [`minAnswer` and `maxAnswer` values](https://github.com/smartcontractkit/libocr/blob/9e4afd8896f365b964bdf769ca28f373a3fb0300/contract/AccessControlledOffchainAggregator.sol#L33). On most data feeds, these values are no longer used and they do not stop your application from reading the most recent answer. For monitoring purposes, you must decide what limits are acceptable for your application. Configure your application to detect when the reported answer is close to reaching reasonable minimum and maximum limits so it can alert you to potential market events. Separately, configure your application to detect and respond to extreme price volatility or prices that are outside of your acceptable limits. ### Check the timestamp of the latest answer Chainlink Data Feeds do not provide streaming data. Rather, the aggregator updates its `latestAnswer` when the value deviates beyond a specified threshold or when the heartbeat idle time has passed. You can find the heartbeat and deviation values for each data feed at [data.chain.link](https://data.chain.link/) or in the [Contract Addresses](/data-feeds/price-feeds/addresses) lists. Your application should track the `latestTimestamp` variable or use the `updatedAt` value from the `latestRoundData()` function to make sure that the latest answer is recent enough for your application to use it. If your application detects that the reported answer is not updated within the heartbeat or within time limits that you determine are acceptable for your application, pause operation or switch to an alternate operation mode while identifying the cause of the delay. When the node detects that the heartbeat is reached, it initiates the latest round. Depending on congestion and network conditions, there may be a slight delay for the latest round to get onchain. During periods of low volatility, the heartbeat triggers updates to the latest answer. Some heartbeats are configured to last several hours, so your application should check the timestamp and verify that the latest answer is recent enough for your application. Users should build applications with the understanding that data feeds for wrapped or liquid staking assets might have different heartbeat and deviation thresholds than that of the underlying asset. Heartbeat and deviation thresholds can also differ for the same asset across different blockchains. Combining data from multiple feeds, even those with a common denominator, might result in a margin of error that users must account for in their risk mitigation practices. To learn more about the heartbeat and deviation threshold, read the [Decentralized Data Model](/architecture-overview/architecture-decentralized-model?parent=dataFeeds#aggregator) page. --- # Price Feed Contract Addresses Source: https://docs.chain.link/data-feeds/price-feeds/addresses Live values such as feed contract addresses are not inlined here. Wait for the official API to obtain current data. --- # Price Feeds Source: https://docs.chain.link/data-feeds/price-feeds Chainlink Data Feeds provide data that is aggregated from many data sources by a decentralized set of independent node operators. The [Decentralized Data Model](/architecture-overview/architecture-decentralized-model?parent=dataFeeds) describes this in detail. However, there are some exceptions where data for a feed can come only from a single data source or where data values are calculated. Read the [Selecting Quality Data Feeds](/data-feeds/selecting-data-feeds) to learn about the different data feed categories and how to identify them. --- # Rate and Volatility Feed Addresses Source: https://docs.chain.link/data-feeds/rates-feeds/addresses Live values such as feed contract addresses are not inlined here. Wait for the official API to obtain current data. --- # Rate and Volatility Feeds Source: https://docs.chain.link/data-feeds/rates-feeds Chainlink rate and volatility feeds provide data for interest rates, interest rate curves, and asset volatility. You can read these feeds the same way that you read other Data Feeds. Specify the [Rate or Volatility Feed Address](/data-feeds/rates-feeds/addresses) that you want to read instead of specifying a Price Feed address. See the [Using Data Feeds](/data-feeds/using-data-feeds) page to learn more. The following data types are available: - [Bitcoin Interest Rate Curve](#bitcoin-interest-rate-curve) - [ETH Staking APR](#eth-staking-apr) - [Realized Volatility](#realized-volatility) ## Bitcoin Interest Rate Curve Lenders and borrowers use base rates to evaluate interest rate risk for lending and borrowing contracts, asset valuation for derivatives contracts, and an underlying rate for interest rate swap contracts. Bitcoin Interest Rate Curve Data Feeds provide a base rate to assist with market decisions and quantify the risks of using certain protocols and products based on current and predicted baseline interest rates. The curve's normalized methodology and daily rates introduce more consistency and predictability to the ebb and flow of digital asset markets. Bitcoin Interest Rate Curve Feeds incorporate a wide range of data sources such as OTC lending desks, DeFi lending pools, and perpetual futures markets. To learn more about the use of these interest rate curves in the industry, read the [Bitcoin Interest Rate Curve (CF BIRC)](https://blog.chain.link/cf-bitcoin-interest-rate-curve-cf-birc/) blog post. See the [Rate and Volatility Feed Addresses](/data-feeds/rates-feeds/addresses) page to find the Bitcoin Interest Rate Curve feeds that are currently available. ## ETH Staking APR The ETH Staking APR feeds provide a trust-minimized and tamper-proof source of truth for the global rate of return from staking as a validator to secure the Ethereum network. The annualized rate of return is calculated over 30-day and 90-day rolling windows. Data providers use offchain computation to calculate returns at an epoch level, reach consensus on the APR, and then write the results onchain to be used by decentralized protocols and Web 3 applications. Feeds are currently configured to update at a minimum of once per day. ![A diagram showing how Staking data is obtained and annualized rate of return is confirmed onchain](/images/data-feed/eth-staking-apr-feed-V3.webp) See the [Rate and Volatility Feed Addresses](/data-feeds/rates-feeds/addresses) page to find the ETH Staking APR feeds that are currently available. If you have questions or would like to request an enhancement to ETH Staking APR feeds, contact us using [this form](https://forms.gle/Jc1MGY5ry1ceYH1X6). ## Realized volatility Realized volatility measures asset price movement over a specific time interval. This value is expressed as a percent of the asset price. The more an asset price moves up or down over time, the higher the realized volatility is for that asset. Please note that realized volatility is not the same as implied volatility, which measures the market's expectation about future volatility typically derived from options markets. Each data feed reflects the volatility of an asset over a specific rolling window of time. For example, some data feeds provide volatility data for the last 24 hours, 7 days, and 30 days of time. You can compare the data across these windows to infer whether the volatility of an asset is trending up or down. For example, if realized volatility for the 24-hour window is higher than the 7-day window, volatility might increase. The same high-quality data providers used in Chainlink's price feeds sample price data every 10 minutes to refresh volatility estimates. onchain values are updated when the feed heartbeat or deviation threshold is met. ![A diagram showing how volatility data is obtained and answers for different volatility windows is confirmed onchain](/images/data-feed/realised-volatility-feed-V3.webp) See the [Rate and Volatility Feed Addresses](/data-feeds/rates-feeds/addresses) page to find heartbeat and deviation information for each feed. If you have questions or would like to request an enhancement to Realized Volatility Feeds, contact us using [this form](https://forms.gle/Jc1MGY5ry1ceYH1X6). --- # Selecting Quality Data Feeds Source: https://docs.chain.link/data-feeds/selecting-data-feeds When you design your applications, consider the quality of the data that you use in your smart contracts. Ultimately you are responsible for identifying and assessing the accuracy, availability, and quality of data that you choose to consume via the Chainlink Network. Note that all feeds contain some inherent risk. Read the [Risk Mitigation](#risk-mitigation) and [Evaluating Data Sources](#evaluating-data-sources-and-risks) sections when making design decisions. Chainlink lists decentralized data feeds in the documentation to help developers build new applications integrated with data. Please review this page in its entirety to understand the risks and categories relevant to your data feed implementation. For a summary of data sourcing models by asset type, see [Data Sources](/data-feeds/data-sources). ## Data Feed Categories This categorization is put in place to inform users about the intended use cases of feeds and help highlight some of the inherent market integrity risks surrounding the data quality of these feeds. Data feeds are grouped into the following categories based on the level of market pricing risk, based on multiple factors, from lowest to highest: - [🟢 Low Market Pricing Risk](#-low-market-pricing-risk-feeds) - [🟡 Medium Market Pricing Risk](#-medium-market-pricing-risk-feeds) - [🟠 High Market Pricing Risk](#-high-market-pricing-risk-feeds) - [🔴 Very High Market Pricing Risk](#-very-high-market-pricing-risk-feeds) - [🆕 New Token Feeds](#-new-token-feeds) - [🔵 Custom Feeds](#-custom-feeds) - [⭕ Deprecating](#-deprecating) - [⚪ Unrated](#-unrated) If a feed does not display one of these categories, it has not yet been evaluated for market pricing risk. Treat it as unevaluated and use it at your own risk. > **NOTE** > > For important updates regarding the use of Chainlink Data Feeds, users should join the official Chainlink Discord and > subscribe to the [#data-feeds channel](https://discord.gg/Dqy5N9UbsR) and regularly check the [Chainlink > Changelog](https://dev.chain.link/changelog?product=Data+Feeds). ## Market pricing risk by category These subsections describe standard market price feeds at each pricing risk level and correspond to the [category list](#data-feed-categories) above. ### 🟢 Low Market Pricing Risk Feeds These are data feeds that follow a standardized data feeds workflow to report market prices for an asset pair. Chainlink node operators each query several sources for the market price and aggregate the estimates provided by those sources. Low Market Pricing Risk feeds have the following characteristics: - More resilient to disruption than other feeds - Leverage multiple data sources when they are available - Higher volumes across multiple markets enables price discovery While Market Pricing Risk may be categorized as low, other risks might still exist based on your use case, data provider availability or performance, the blockchain on which the feed is deployed, and the conditions on that chain. Developers remain responsible for ensuring that protocol [risk parameters are configured appropriately](#risk-mitigation) and that the operation and performance of Low Market Pricing Risk data feeds match expectations. For users integrating a custom feed, please review the [Custom Feed](#-custom-feeds) section for additional considerations. ### 🟡 Medium Market Pricing Risk Feeds These feeds also follow a standardized data feeds workflow to report market prices for an asset pair. The pair in question may have features that make it more challenging to reliably price, or potentially subject it to volatility, which may pose a risk in some use cases. While the architecture of these feeds is resilient and distributed, these feeds carry additional Market Pricing Risk. Types of Market Pricing Risk that may lead to a feed being categorized as Medium Market Pricing Risk include: - Lower or inconsistent asset volume may result in periods of low liquidity in the market for such assets. This, in turn, can lead to volatile price movements. - A spread between the price for this asset on different trading venues or liquidity pools. - Market Concentration Risk: If the volume for a given asset is excessively concentrated on a single exchange, that trading venue could become a single point of failure for the feed. - Cross-Rate Risk: The base asset trades in large volumes against assets that are not pegged to the quote asset. As a result, the price of this specific asset pair may fluctuate even if the underlying asset is not being traded. - The asset is going through a significant market event such as a token or liquidity migration. - The asset has a high spread between data providers, the root cause of which is often one of the above factors. - The availability of pricing sources may be subject to change based on concentration, trading venue location, and currency pairs. Developers remain responsible for ensuring that protocol [risk parameters are configured appropriately](#risk-mitigation) and that the operation and performance of Medium Market Pricing Risk data feeds matches expectations. For users integrating a custom feed, please review the [Custom Feed](#-custom-feeds) section for additional considerations. ### 🟠 High Market Pricing Risk Feeds These feeds also follow a standardized data feeds workflow to report market prices for an asset pair. However, the pair in question often exhibits a heightened degree of some of the risk factors outlined under Medium Market Pricing Risk, or a separate risk that makes the market price subject to uncertainty or volatility. In using a High Market Pricing Risk data feed you acknowledge that you understand the risks associated with such a feed and that you are solely responsible for monitoring and mitigating such risks. Developers remain responsible for ensuring that protocol [risk parameters are configured appropriately](#risk-mitigation) and that the operation and performance of High Market Pricing Risk data feeds matches expectations. High Market Pricing Risk data feeds may be deprecated. See the [Data Feed Shutdown Policy](#data-feed-shutdown-policy) for more information. For users integrating a custom feed, please review the [Custom Feed](#-custom-feeds) section for additional considerations. ### 🔴 Very High Market Pricing Risk Feeds Very High Market Pricing Risk feeds price assets with quotes that are subject to extreme levels of risk, greater than those outlined above for High Market Pricing Risk. Types of Market Pricing Risk that may lead to a feed being categorized as Very High Market Pricing Risk include, but are not limited to: - The asset is going through a significant market event such as a hack, bridge failure, or a delisting from a major exchange. - The asset or project is being deprecated in the market. - Volumes have dropped to extremely low levels. - Reliable pricing sources for asset are extremely limited. Users should wind down their reliance on these feeds and/or implement strict capital and risk management policies accounting for extreme price and market structure volatility. Very High Market Pricing Risk feeds will be wound down over time in accordance with the [Data Feed Shutdown Policy](#data-feed-shutdown-policy). In using a Very High Market Pricing Risk data feed you acknowledge that you understand the risks associated with such a feed and that you are solely responsible for monitoring and mitigating such risks. You understand that Chainlink may not provide separate monitoring for these feeds. Developers remain responsible for ensuring that protocol [risk parameters are configured appropriately](#risk-mitigation) and that the operation and performance of Very High Market Pricing Risk data feeds matches expectations. For users integrating a custom feed, please review the [Custom Feed](#-custom-feeds) section for additional considerations. ### 🆕 New Token Feeds When a token is newly launched, the historical data required to implement a rigorous risk assessment framework that would allow the categorization of a market data feed for that token as Low, Medium, or High Pricing Risk is unavailable. Consistent price discovery may involve an indeterminate amount of time. Users must understand the additional [market and volatility risks](#evaluating-data-sources-and-risks) inherent with such assets. Users of New Token feeds are responsible for independently verifying the liquidity and stability of the assets priced by the feeds that they use. At the end of a probationary period, the status of New Token feeds may be adjusted to Very High, High, Medium, or Low Market Pricing Risk, or in rare cases be deprecated entirely. For users integrating a custom feed, please review the [Custom Feed](#-custom-feeds) section for additional considerations. ### 🔵 Custom Feeds Custom Feeds are built to serve a specific use case and can differ materially from standard market price feeds in sourcing, methodology, update behavior, and risk profile. Users must evaluate the properties of a custom feed to make sure it aligns with their intended use case and closely assess the risk of [single source feeds](#single-source-data-providers). [Contact the Chainlink Labs team](https://chain.link/contact?ref_id=pricefeeds) if you want more detail on any specific feeds in this category. Custom Feeds have the following categories and compositions: - [🟠](#-high-market-pricing-risk-feeds) **Onchain Single Source Feeds:** These feeds take their data from an onchain source, however, the feed has only a single data provider currently supporting the feed. - [🟡](#-medium-market-pricing-risk-feeds) **Onchain Proof of Reserve Feeds:** Chainlink Proof of Reserve uses a large decentralized collection of security-reviewed and Sybil-resistant node operators to acquire and verify reserve data. In this use case, reserves reside onchain. - [🟡](#-medium-market-pricing-risk-feeds) **Exchange Rate Feeds:** These feeds read an exchange rate from an external contract onchain that is designed to allow conversion from one token to another. Chainlink does not own or control these contracts in any way. They are not equivalent to market price feeds. - [🟡](#-medium-market-pricing-risk-feeds) **Technical Feeds:** Feeds within this category measure a particular technical metric from a specified blockchain. For example, Fast Gas or Block Difficulty. - [🟡](#-medium-market-pricing-risk-feeds) **Total Value Locked Feeds:** These feeds measure the total value locked in a particular protocol. - [🟠](#-high-market-pricing-risk-feeds) **Custom Index Feeds:** An index calculates a function of the values for multiple underlying assets. The function is specific to that index and is typically calculated by node operators following an agreed formula. - [🟠](#-high-market-pricing-risk-feeds) **24/7 Extended-Hours Feeds:** Custom feeds that publish outside an asset's standard market hours, using a methodology specific to the asset class, such as blending a reference price with tokenized asset streams for precious metals, or sourcing a crypto-derived rate for select forex pairs. See [24/7 Extended-Hours Data Feeds](/data-feeds/24-7-extended-hours-data-feeds) for methodology and risk considerations. - [🟠](#-high-market-pricing-risk-feeds) **Offchain Single Source Feeds:** Some data providers use a single data source, which might be necessary if only one source exists offchain for a specific type of data. - [🟠](#-high-market-pricing-risk-feeds) **Offchain Proof of Reserve Feeds:** Chainlink Proof of Reserve uses a large decentralized collection of security-reviewed and Sybil-resistant node operators to acquire and verify reserve data. In this use case, reserves reside offchain. - [🟠](#-high-market-pricing-risk-feeds) **LP Token Feeds:** These feeds use a decentralized feed for the underlying asset as well as calculations to value the liquidity pool (LP) tokens. - [🔴](#-very-high-market-pricing-risk-feeds) **Wrapped Calculated Feeds:** These feeds are typically pegged 1:1 to the underlying token or asset. Under normal market conditions, these feeds track their underlying value accurately. However, given that the price is a derivative formed from a calculated method, the derivative asset may not always precisely track the value of the underlying token or asset. If you plan on using one of these feeds and would like to get a more detailed understanding, [contact the Chainlink Labs team](https://chain.link/contact?ref_id=pricefeeds). Using feeds that were not specifically designed for your use case involves risk. Their use might pose risks that could result in harm to your project. Users are responsible for thoroughly vetting and validating such deployments and determining their suitability. You bear responsibility for any manner in which you use the Chainlink Network, its software, and documentation. ### ⭕ Deprecating These feeds are being deprecated. To find the deprecation dates for specific feeds, see the [Feeds Scheduled For Deprecation](/data-feeds/deprecating-feeds) page. #### Data Feed Shutdown Policy Data feeds managed by Chainlink Labs will be considered for deprecation if they pose a risk to the Chainlink Community and broader ecosystem, if the asset or assets (or their volume or liquidity) on the feed have significantly deteriorated or are unreliable and no longer meet our Quality Assurance standards, or if the data feed has become economically unsustainable to support. Users are therefore advised to ensure they have measures in place for the orderly deprecation of reliance on specific Chainlink price feeds. Known users of these feeds will be contacted directly about the feeds' deprecation whenever possible. Notifications will also be posted on the [Feeds Scheduled For Deprecation](/data-feeds/deprecating-feeds) page and on our [Discord channel](https://discord.com/channels/592041321326182401/991444378335838318) with two weeks of notice before they are shut down except where conditions require an accelerated deprecation. > **NOTE: Data Feed Shutdown Behavior** > > Once a feed is shutdown, calls to the feed will behave as follows: > > - `latestAnswer` will return `0` > - `latestRoundData` will revert with no data available ### ⚪ Unrated These feeds have not yet been evaluated for market pricing risk. Treat them as unevaluated and use them at your own risk. ## Market hours In addition to categories, be aware that markets for several assets are actively traded only during certain hours. Listed data feeds include an attribute describing their market hours. Chainlink Labs recommends using these feeds only during their specified hours: | Feed | Hours | | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Crypto** | 24/7/365 - No market close. | | **US_Equities** | Standard US equity market hours: 09:30 - 16:00 ET M-F excluding US equity market holidays. | | **US_Equities_24/5** | 24 hours a day, 5 days a week: 18:00 ET Sunday to 17:00 ET Friday. These feeds aggregate data across regular, pre-market, post-market, and overnight equity trading sessions. Used by [tokenized equity feeds](/data-feeds/tokenized-equity-feeds) that require continuous equity pricing outside of standard market hours. Feeds are not updated on weekends or US equity market holidays. | | **UK_ETF** | Standard UK equity market hours: 08:00 - 16:30 UK time M-F excluding UK equity market holidays. | | **Forex** | 18:00 ET Sunday to 17:00 ET Friday. The feeds also follow the global Forex market Christmas and New Year's Day holiday schedule. Many non-G12 currencies primarily trade during local market hours. It is recommended to use those feeds only during local trading hours. We generally observe normal trading within these hours. Prices outside of these hours may be subject to volatility and users are advised to implement additional controls. In addition, local holidays, natural disasters, political unrest or other exogenous shocks are liable to interrupt normal trading in less-liquid currencies. Some currency pairs are instead offered as [24/7 forex feeds](/data-feeds/24-7-extended-hours-data-feeds#forex), which source a crypto-derived rate and publish continuously; review that page's risk considerations before using one. | | **Precious_Metals** | 18:00 ET Sunday to 17:00 ET Friday with a one-hour break Monday through Thursday from 17:00 to 18:00. The feeds also follow the global Forex market holiday schedule for Christmas and New Year's Day. Some [24/7 blended precious metals feeds](/data-feeds/24-7-extended-hours-data-feeds#precious-metals) publish outside these hours using a custom methodology. | | **NYMEX** (US OIL) | 18:00 ET Sunday to 17:00 ET Friday, with a one-hour break Monday through Thursday from 17:00 to 18:00. The feed also follows the NYMEX market holiday schedule. | | **COMEX** (Non precious metals) | 18:00 ET Sunday to 17:00 ET Friday with a one-hour break Monday through Thursday from 17:00 to 18:00. The feed also follows the COMEX market holiday schedule. | | **CBOT** (Agricultural) | Monday - Thursday 00:00-08:45, 09:30-00:00 and Friday 00:00-08:45, 09:30-14:00 ET, excluding CBOT market holidays. | | **MI_ETF** | Standard Milan equity market hours. 09:00-17:30 CET time M-F, excluding Milan equity market holidays. | | **XETRA_ETF** | Deutsche Börse Xetra equity market hours. 09:00-17:30 CET time M-F, excluding Xetra equity market holidays. | ## Risk Mitigation As a development best practice, design your systems and smart contracts to be resilient and mitigate risk to your protocol and your users. Ensure that your systems can tolerate known and unknown exceptions that might occur. Some examples include but are not limited to volatile market conditions, reduced price discovery availability, the degraded performance of infrastructure, chains, or networks, and any other upstream outage related to data providers or node operators. You bear responsibility for any manner in which you use the Chainlink Network, its software, and documentation. To help you prepare for unforeseen market events, you should take additional steps to protect your application or protocol regardless of the Market Pricing Risk categorization of the Data Feeds your application consumes. The below tooling is put in place to mitigate extreme market events, possible malicious activity on third-party venues or contracts, potential delays, performance degradation, and outages. Below are some examples of tooling that Chainlink users have put in place: - **Circuit breakers:** In the case of an extreme price event, the contract would pause operations for a limited period of time. [Chainlink Runtime Environment](/cre) is able to monitor data feeds to identify unexpected events. If an event were to occur, the Automation network can send an onchain transaction to pause or halt contract functionality. - **Contract update delays:** Contracts would not update until the protocol had received a recent fresh input from the data feed. - **Manual kill switch:** If a vulnerability or bug is discovered in one of the upstream contracts, the user can manually cease operation and temporarily sever the connection to the data feed. - **Monitoring:** Some users create their own monitoring alerts based on deviations in the data feeds that they are using. - **Soak testing:** Users are strongly advised to thoroughly test price feed integrations and incorporate a [soak period](https://en.wikipedia.org/wiki/Soak_testing) prior to providing access to end users or securing value. - **Provider/source dependency controls:** For single-source feeds, implement controls that detect stale, delayed, missing, or unexpected values from the underlying provider. Where possible, compare the feed against independent references, enforce value bounds or caps, and define fallback or pause behavior before securing value with the feed. For more detailed information about some of these examples, see the [Monitoring data feeds](/data-feeds/#monitoring-data-feeds) documentation. For important updates regarding the use of Chainlink Price Feeds, users should join the official Chainlink Discord and subscribe to the [data-feeds-user-notifications channel](https://discord.gg/Dqy5N9UbsR). ### Stablecoin-specific considerations To protect against extreme stablecoin price events, protocols should assess upside risk and evaluate setting an explicit valuation ceiling for each stablecoin, along with mechanisms that provide proactive peg protection. One example of this approach is the Correlated-assets Price Oracle ([CAPO](https://app.aave.com/governance/v3/proposal/?proposalId=51)), which was implemented by Aave Labs with support from Chainlink Labs and adds upside protection when valuing fiat-pegged stablecoins while preserving full downside responsiveness. Other risk mitigation measures may be appropriate depending on a protocol's intended use case. Teams interested in developing similar risk mitigation measures for stablecoins can [contact the Chainlink Labs team](https://chain.link/contact?ref_id=pricefeeds). Developers remain responsible for ensuring that protocol risk parameters are configured appropriately and that the operation and performance of stablecoin data feeds matches expectations. ## Chainlink Community Deployments Chainlink technology is used by many within the blockchain community to support their use cases. Deployments built and run by community members are not tracked in the Chainlink documentation. Chainlink's community is continuously growing, and they play a vital role in developing the ecosystem, so the software and tooling are developed for anyone to use. Users have a wide variety of options for choosing how to deliver data onchain. They can deploy Chainlink nodes themselves or via the extensive network of node operators that offer services and access one of the community-managed oracle networks that support the supply of various types of data onchain. Chainlink Labs does not take responsibility for the use of Chainlink node software. It is always recommended that you conduct a thorough analysis of your requirements and carry out appropriate due diligence on any partners you wish to use with your project. The Chainlink Labs team does not monitor community deployments and users should use best practices in observability, monitoring, and risk mitigation as appropriate for your application's stage of development and use case. As your usage of data feeds evolves and requirements for higher availability and greater security increases, such as securing substantive value, the reliability properties of your data feed will become crucial. [Contact Chainlink Labs team](https://chain.link/contact?ref_id=pricefeeds) for services to ensure deployments meet the highest levels of availability and security. **High Risk: Forked, modified, or custom software:** As Chainlink is open source, independent forks and modifications may exist. Chainlink Labs and development teams are not involved in these and do not track or maintain visibility on them. Chainlink Labs is not responsible for updates, enhancements, or bug fixes for these versions, and Chainlink Labs does not monitor them. Their use might pose risks that can do harm to your project. Users are responsible for thoroughly vetting and validating such deployments and determining their suitability. ## Evaluating Data Sources and Risks If your smart contracts use data feeds, assess those data feeds for the following characteristics. For a summary of data sourcing models by asset type, see [Data Sources](/data-feeds/data-sources). - [Liquidity and its Distribution](#liquidity-and-its-distribution) - [Single Source Data Providers](#single-source-data-providers) - [Crypto and Blockchain Actions](#crypto-and-blockchain-actions) - [Market Failures Resulting from Extreme Events](#market-failures-resulting-from-extreme-events) - [Periods of High Network Congestion](#periods-of-high-network-congestion) - [Unknown and Known Users](#unknown-and-known-users) - [DEX volumes](#dex-volumes) ### Liquidity and its Distribution If your smart contract relies on pricing data for a specific asset, make sure that the asset has a sufficiently healthy level of liquidity in the market to avoid price and market manipulation. Assets with low liquidity or volume can be volatile or difficult to price, which might negatively impact your application and its users. Malicious actors might try to exploit volatility or periods of reduced trading activity to take advantage of the logic in a smart contract and cause it to execute in a way that you did not intend. Some data feeds obtain their pricing data from individual exchanges rather than from aggregated price tracking services that gather their data from multiple exchanges. These are marked as such in the docs page for that feed. Assess the liquidity and reliability of that specific exchange. Liquidity migrations occur when a project moves its tokens from one liquidity provider (such as a DEX, a CEX, or a new DeFi application) to another. When liquidity migrations occur, it can result in low liquidity in the original pool, making the asset susceptible to market manipulation. If your project is considering a liquidity migration, you should coordinate with relevant stakeholders, including liquidity providers, exchanges, oracle node operators, Data Feed providers, and users, to ensure prices are accurately reported throughout the migration. Feeds for assets with low market liquidity or volume where data providers exhibit an abnormal price spread may, on occasion, see a price oscillate between two or more price points within regular intervals. To mitigate risk associated with such price oscillation, users must regularly monitor & assess the quality of an asset's liquidity. Similarly, assets with low market liquidity may experience abnormal or volatile price movements due to erroneous trades. Design and test your contracts to handle price spikes and implement risk management measures to protect your assets. For example, create mock tests that return various oracle responses. ### Single Source Data Providers Some Chainlink Data Feeds rely on a single underlying data source or a single data provider. This can be necessary when only one authoritative source exists onchain or offchain for a specific type of data, such as a protocol-published NAV, reserve value, redemption rate, exchange rate, or other custom metric. Single-source feeds have a different risk profile than market price feeds that aggregate observations across multiple data sources and node operators. If the underlying source or provider publishes incorrect data, omits data, changes its methodology, experiences an outage, is delayed, or is compromised, the feed may reflect that issue. Any such error, omission, delay, or provider failure could negatively affect your application and its users. Before using a single-source feed, determine whether it is suitable for your use case and whether your application can tolerate the provider/source risk. This is especially important when the feed is used to secure value, determine collateral value, trigger liquidations, support minting or redemption, calculate NAV, or automate other protocol actions. Users of single-source feeds MUST implement additional safeguards such as value bounds, caps, circuit breakers, freshness checks, fallback behavior, monitoring against independent references where available, and manual pause or kill-switch controls. Single-source feeds can be classified as High or Very High Market Pricing Risk depending on the asset, methodology, provider dependency, and intended use case. For example, the [USD / ARS feed](https://data.chain.link/feeds/ethereum/mainnet/usd-ars) is a [24/7 forex feed](/data-feeds/24-7-extended-hours-data-feeds#forex) that sources a single data provider's crypto-derived rate, which is constructed differently from, and can diverge materially from, Argentina's official, MEP, or CCL exchange rates. Confirm this rate construction matches your use case's definition of the currency pair before relying on it, particularly for anything securing value or triggering liquidations. If you are using or considering a single-source feed in production, [contact the Chainlink Labs team](mailto:chainlink_data_feeds@smartcontract.com) to discuss the feed's methodology, provider dependency, and appropriate application-level mitigations. ### Crypto and Blockchain Actions Price data quality is subject to crypto actions by the crypto and blockchain project teams. Crypto actions are similar to [corporate actions](https://en.wikipedia.org/wiki/Corporate_action) but are specific to cryptocurrency and blockchain projects, such as token renaming, token swaps, redenominations, splits, reverse splits, network upgrades, and other migrations that teams or communities who govern the blockchain or token might undertake. Sustaining data quality is dependent on data sources implementing the necessary adjustments related to such actions. For example, when a project upgrades to a new version of their token, this results in a token migration. When token migrations occur, they require building a new Data Feed to ensure that the token price is accurately reported. Similarly, actions by blockchain project teams or communities, such as forks or upgrades to the network, may require new Data Feeds to ensure continuity and data quality. When considering a token migration, fork, network upgrade, or other crypto action, projects should proactively reach out to relevant stakeholders to ensure the asset price is accurately reported throughout the process. ### Market Failures Resulting from Extreme Events Users are strongly advised to set up monitoring and alerts in the event of unexpected market failures. Black swan events, hacks, coordinated attacks, or extreme market conditions may trigger unanticipated outcomes such as liquidity pools becoming unbalanced, unexpected re-weighting of indices, abnormal behavior by centralized or decentralized exchanges, or the de-pegging of synthetic assets, stablecoins, and currencies from their intended exchange rates. Circuit breakers can be created using [Chainlink Automation](https://docs.chain.link/chainlink-automation). Circuit breakers are safety measures that monitor data feeds for unexpected scenarios such as stale prices, drastic price changes, or prices approaching a predetermined min/max threshold. If an unexpected scenario occurs, the circuit breaker can send an onchain transaction to pause or halt contract functionality. ### Periods of High Network Congestion Data Feed performance relies on the chains they are deployed on. Periods of high network congestion or network downtime might impact the frequency of Chainlink Data Feeds. It is advised that you configure your applications to detect such chain performance or reliability issues and to respond appropriately. ### Unknown and Known Users Routine maintenance is carried out on Chainlink Data Feeds, including decommissioning, on an ad-hoc basis. These maintenance periods might require users to take action in order to maintain business continuity. Notifications are sent to inform known users regarding such occurrences, and it is strongly encouraged for all users, including those users utilizing data feeds for offchain purposes, [to provide their contact information](https://chain.link/contact?ref_id=pricefeeds) before utilizing data feeds. Without providing contact information, users will be unable to receive notifications regarding important Data Feed updates. If you are using Data Feeds but have not provided your contact information, you can do so [here](https://chain.link/contact?ref_id=pricefeeds). Users that fail to provide notification information do so at their own risk. ### DEX Volumes Assets with a significant market presence on decentralized exchanges (DEXs) face distinct risks related to unique market structure. The market integrity can be compromised by flash loan-funded attacks, volume shifts to different onchain or offchain exchanges, or a well-capitalized actor temporarily manipulating the price on that exchange. Additionally, DEX trades can result in slippage due to liquidity migrations and trade size. The likelihood of high-slippage trades being accurately reflected in market prices depends on the trading patterns of the asset. Generally, a lower risk of deviant trades impacting aggregated prices is associated with assets having multiple DEX pools with healthy volumes and consistent trading activity across different time windows. ## Evaluating Wrapped or Bridged Assets ### Assessing how to Price Wrapped or Bridged Assets When assessing a Chainlink Data Feed for a wrapped or bridged asset such as WBTC, users should evaluate the tradeoffs between using a Data Feed specifically built for the wrapped or bridged asset or a Data Feed built for the underlying asset. Decisions should be made on a case-by-case basis considering the liquidity, depth, and trading volatility of the underlying asset compared to its derivative. In addition, users must consider the security mechanism that is designed to keep the wrapped or bridged asset coupled to its underlying asset. Review these parameters regularly as asset dynamics continuously evolve. ### Extreme Events Causing Price Deviations in Wrapped or Bridged Assets Chainlink Data Feeds are designed to provide the market-wide price of various assets, as determined by a volume-weighted average across a wide range of exchanges. On blockchain networks where assets are wrapped and/or bridged from another environment using a cross-chain token bridge, Chainlink Data Feeds built for the underlying asset will continue to report the market-wide price of the underlying asset as opposed to the price of the wrapped/bridged asset. This methodology reduces risks around market manipulation because wrapped/bridged tokens are often less liquid than the underlying asset. However, users should be aware that certain extreme events may result in price deviations between the wrapped/bridged asset and its underlying counterpart. For example, the exploitation or hack of a cross-chain token bridge may cause a collapse in demand for a particular wrapped asset. As such, users should construct their applications with safeguards, such as circuit breakers to proactively pause functionality to mitigate risk during such scenarios. Circuit breakers can be created using Chainlink Automation to monitor data feeds for unexpected scenarios. An additional mechanism for securing a protocol utilizing wrapped assets is by incorporating [Chainlink Proof of Reserve](https://chain.link/proof-of-reserve). Chainlink Proof of Reserve enables the real-time reserve monitoring of offchain and cross-chain assets, including those that have been wrapped/bridged. By comparing the wrapped token's supply against a Chainlink Proof of Reserve feed, protocols can ensure that these assets are properly collateralized at all times. ### Front Running Risk Front running (when a third party benefits from prior access to information about a transaction) is a known risk inherent to specific blockchain applications. Chainlink Data Feeds are optimized to prioritize high levels of data quality and reliability over latency. To mitigate the risk associated with front running, users building highly latency-dependent applications should assess whether the configuration of data feeds meets their needed specifications for speed and frequency. [Chainlink Data Streams](https://docs.chain.link/data-streams) serve as an alternative solution to Data Feeds for latency-sensitive applications, providing low-latency delivery of market data offchain that can be verified onchain while mitigating front running. ## Market Price Feeds Market Price Feeds incorporate three layers of aggregation at the data source, node operator, and oracle network layers. These feeds are designed to reflect observable market prices across [major venues](https://blog.chain.link/chainlink-price-feeds-secure-defi/). ### Bounded Market Price Feeds Bounded Market Price Feeds are a specific category of Market Price Feeds that incorporate predefined bounds on reported values to mitigate specific asset class risks, following risk management best practices. For example, a Bounded Market Price Feed for a stablecoin would include an upper bound that caps the on-chain reported price at a pre-defined maximum (e.g., $1.05), even during edge cases like market dislocations where the stablecoin trades higher than the cap. In such cases, the price will not be reported on-chain. Instead, the most recently written on-chain price will be the value returned by calling [latestRoundData](https://docs.chain.link/data-feeds/api-reference#latestrounddata) on the feed's [proxy address](https://docs.chain.link/data-feeds#single-value-feed-components). This helps prevent extreme or sustained premium pricing from being reported. These bounds are designed to provide guardrails for assets where structural deviations may introduce elevated risk for feed consumers. Note that a Bounded Market Price Feed for a stablecoin would not include a lower bound for the on-chain reported price. In rare circumstances, the fundamental value of an asset underlying a Bounded Market Price Feed, such as a stablecoin, may materially change. In such a case, the predefined bound(s) associated with a Bounded Market Price Feed may be adjusted or removed following an update to Chainlink's [Changelog](https://dev.chain.link/changelog). Users must be aware that Bounded Market Price Feeds have unique risk considerations. Developers remain responsible for ensuring that protocol risk parameters are configured appropriately and that the operation and performance of Bounded Market Price Feeds matches expectations. If your use case requires the use of a non-Bounded Market Price Feed and one does not exist, contact us at [chainlink_data_feeds@smartcontract.com](mailto:chainlink_data_feeds@smartcontract.com). ## Exchange Rate Feeds The architecture of exchange rate feeds differs from that of [Market Price Feeds](#market-price-feeds). **Exchange rate feeds** are tied to specific protocols or ecosystems and report the internal redemption rates for an asset (i.e., the value/rate at which an asset can be redeemed or exchanged within that protocol's ecosystem). This data is sourced directly from a specified smart contract on a source chain and relayed to a destination chain. Exchange rate feeds are useful in circumstances such as: - **Pricing yield-bearing assets:** Multiplying a yield-bearing asset's exchange rate by the underlying asset's market rate can be used to calculate the yield-bearing asset's current price. This methodology can reduce certain pricing volatility risks associated with lower-liquidity yield-bearing assets. - **Enabling cross-chain staking:** For example, liquid staking tokens (LSTs) or liquid restaking tokens (LRTs) can be minted at the exchange rate on a layer 2, while Chainlink CCIP transfers the underlying asset to a layer 1 for staking. - **Improving liquidity pool performance for yield-bearing assets:** Exchange rate feeds can be utilized to programmatically adjust swap curves to maximize liquidity efficiency. > **NOTE** > > Users must be aware that both market price and exchange rate pricing methodologies have unique risk considerations and > risk mitigation strategies that vary based on an asset's type and liquidity profile. Users are responsible for > decisions relating to feed selection. To learn more about how best to leverage Chainlink Data Feeds for your project, > contact us [here](https://chain.link/contact). ## ETF and Forex feeds When you use Data Feeds for ETFs or Foreign Exchange (Forex) data, be aware of the following best practices: - Offchain equity and ETF assets are traded only during [standard market hours](/data-feeds/selecting-data-feeds#market-hours). Do not use these feeds outside those windows. - Assets on the Forex (Foreign Exchange) markets are traded only during [defined market hours](/data-feeds/selecting-data-feeds#market-hours). Additionally, some currencies might trade only during local banking hours. Do not use Forex feeds outside market hours for the specific currency. - UK ETF price feed answers are 15 minutes delayed from their original published source. Assets are traded only during [standard market hours](/data-feeds/selecting-data-feeds#market-hours). Do not use these feeds outside their specified hours. > **NOTE: Disclaimer** > > Developers remain responsible for ensuring that protocol risk parameters are configured appropriately and that the > operation and performance of a Chainlink Data Feed matches expectations. The performance of a Chainlink Data Feed may > be affected by the availability or performance of a data provider. The composition or availability of a Chainlink Data > Feed may change over time. Please review this webpage in its entirety and review the [Chainlink Terms of > Service](https://chain.link/terms) for important information and disclosures. By using Chainlink Data Feeds, you > acknowledge and agree to these terms. --- # Self-Managed Feeds Source: https://docs.chain.link/data-feeds/self-managed-feeds > **NOTE: Contact us** > > Chains and operators interested in deploying self-managed feeds can reach the Data Feeds team at > [chainlink_data_feeds@smartcontract.com](mailto:chainlink_data_feeds@smartcontract.com). **Self-managed feeds** are Data Feeds where values are written onchain by a **third party** (typically the chain or its designated operator), not by Chainlink node operators publishing through Chainlink Labs–managed oracle infrastructure. At a high level, a [Chainlink Runtime Environment (CRE)](/cre) workflow reads from [Chainlink Data Streams](/data-streams) and writes updates to a proxy contract so consumers can read answers using the same familiar Data Feeds interfaces. **Chainlink Labs does not manage the onchain publication path, monitoring, or service levels** for these deployments. The chain (or its operator) is responsible for running the workflow, maintaining contracts, and supporting integrators. **Contract addresses for self-managed feeds are not listed in Chainlink public documentation.** Obtain deployed proxy addresses directly from the chain or its designated operator. ## Responsibilities & guarantees ### Data quality Data for assets are delivered through **[Chainlink Data Streams](/data-streams)**. Data Streams provides the offchain data for the self-managed feeds; however, it is the third party's obligation to read, transform, or write the correct values onchain on schedule. ### Monitoring & service levels **These onchain feeds are not managed or monitored by Chainlink Labs.** Specifically: - Chainlink Labs does **not** provide monitoring for: - Heartbeat compliance - Deviation compliance - End-to-end latency of writing prices onchain - Whether the correct data was written onchain - No service level agreements (SLAs) are provided for self-managed feed publication. - No onchain monitoring specific to these feeds is provided by Chainlink Labs. ### Feed visibility - Proxy addresses for self-managed feeds **do not appear** on official Chainlink public documentation sites (including feed address catalogs). - Integrators must obtain deployed contract addresses **directly from the operators** of the self-managed feeds on each chain. --- # SmartData Feed Addresses Source: https://docs.chain.link/data-feeds/smartdata/addresses Live values such as feed contract addresses are not inlined here. Wait for the official API to obtain current data. --- # SmartData Source: https://docs.chain.link/data-feeds/smartdata Chainlink SmartData is a suite of onchain data offerings designed to unlock the utility, accessibility, and reliability of tokenized real-world assets (RWAs). By providing secure minting assurances alongside essential real-world data such as reserves, Net Asset Value (NAV), and Assets Under Management (AUM) data, the SmartData suite embeds security and enriches data into tokenized RWA offerings. ## SmartData Feed Types SmartData offers two distinct types of data feeds: 1. **Single-value SmartData Feeds**: Similar to traditional price feeds, these provide a single numeric value per feed (like total reserves or NAV). These use the [`AggregatorV3Interface`](/data-feeds/api-reference#aggregatorv3interface) and are read the same way as other Data Feeds. 2. **[Multiple-Variable Response (MVR) Feeds](/data-feeds/mvr-feeds)**: These bundle multiple data points of various types (both numeric and non-numeric) into a single onchain update. MVR feeds use the [`BundleAggregatorProxy` interface](/data-feeds/mvr-feeds/api-reference#ibundleaggregatorproxy) and require a different approach to read and decode the data. ## SmartData Product Categories - [Proof of Reserve Feeds](#proof-of-reserve-feeds) - [NAVLink Feeds](#navlink-feeds) - [SmartAUM Feeds](#smartaum-feeds) ### Proof of Reserve Feeds Proof of Reserves feeds provide the status of reserves for stablecoins, wrapped assets, and real world assets. Proof of Reserve Feeds operate similarly to Price Feeds, but provide answers in units of measurement such as ounces (oz) or number of tokens. To find a list of available Proof of Reserve Feeds, see the [SmartData Feed Addresses](/data-feeds/smartdata/addresses) page. #### Types of Proof of Reserve Feeds Reserves are available for both offchain assets and cross-chain assets. This categorization describes the data reporting variations of Proof of Reserve feeds and helps highlight some of the inherent market risks surrounding the data quality of these feeds. ##### Offchain reserves Offchain reserves are sourced from APIs through an [external adapter](/chainlink-nodes/external-adapters/external-adapters). ![Image](/images/data-feed/off-chain-reserves.webp) Offchain reserves provide their data using the following methods: - Third-party: An auditor, accounting firm, or other third party audits and verifies reserves. This is done by combining both fiat and investment assets into a numeric value that is reported against the token. - Custodian: Reserves data are pulled directly from the bank or custodian. The custodian has direct access to the bank or vault holding the assets. Generally, this works when the underlying asset pulled requires no additional valuation and is simply reported onchain. - ⚠️ Self-reported: Reserve data is read from an API that the token issuer hosts. Reserve data reported by an asset issuer's self-hosted API carries additional risks. Chainlink Labs is not responsible for the accuracy of self-reported reserves data. Users must do their own risk assessment for asset issuer risk. ##### Cross-chain reserves Cross-chain reserves are sourced from the network where the reserves are held. Chainlink node operators can report cross-chain reserves by running an [external adapter](/chainlink-nodes/external-adapters/external-adapters) and querying the source-chain client directly. In some instances, the reserves are composed of a dynamic list of IDs or addresses using a composite adapter. ![Image](/images/data-feed/cross-chain-reserves.webp) Cross-chain reserves provide their data using the following methods: - Wallet address manager: The project uses the [IPoRAddressList](https://github.com/smartcontractkit/chainlink/blob/contracts-v1.3.0/contracts/src/v0.8/interfaces/PoRAddressList.sol) wallet address manager contract and self-reports which addresses they own. Reserve data reported by an asset issuer's self-reported addresses carries additional risks. Chainlink Labs is not responsible for the accuracy of self-reported reserves data. Users must do their own risk assessment for asset issuer risk. - Wallet address: The project reports which addresses they own through a self-hosted API. Reserve data reported by an asset issuer's self-reported addresses carries additional risks. Chainlink Labs is not responsible for the accuracy of self-reported reserves data. Users must do their own risk assessment for asset issuer risk. > **CAUTION: Disclaimer** > > Proof of Reserve feeds can vary in their configurations. Please be careful with the configuration of the feeds used > by your smart contracts. You are solely responsible for reviewing the quality of the data (e.g., a Proof of Reserve > feed) that you integrate into your smart contracts and assume full responsibility for any damage, injury, or any > other loss caused by your use of the feeds used by your smart contracts. > > [Learn more about making responsible data quality decisions.](/data-feeds/selecting-data-feeds#risk-mitigation) ### NAVLink Feeds Chainlink NAVLink Feeds provide real-time, tamper-proof data on the Net Asset Value (NAV) of tokenized assets, funds, or portfolios. NAV is an essential metric in the financial industry for assessing the value of mutual funds, ETFs, and other investment vehicles. It is calculated by subtracting total liabilities from the total assets held within the vehicle. By making NAV data available onchain, developers can build decentralized applications that require accurate and up-to-date valuation metrics. These applications include asset management platforms, DeFi protocols, and investment strategies that rely on NAV for operations such as rebalancing, minting, or redemption. To find a list of available SmartNav Feeds, see the [SmartData Feed Addresses](/data-feeds/smartdata/addresses) page. ### SmartAUM Feeds Chainlink SmartAUM Feeds provide current data on the total market value of assets managed by an entity on behalf of clients. Assets Under Management (AUM) is a crucial indicator used in financial analyses and decision-making processes. By bringing AUM data onchain, decentralized applications can access information for activities such as risk assessment, performance benchmarking, and investment strategy development. To find a list of available Assets Under Management Feeds, see the [SmartData Feed Addresses](/data-feeds/smartdata/addresses) page. ## Using SmartData Feeds ### Using Single-value SmartData Feeds Read answers from single-value SmartData feeds the same way that you read other Data Feeds. Specify the [SmartData feed address](/data-feeds/smartdata/addresses) that you want to read instead of specifying a Price feed address. See the [Using Data Feeds](/data-feeds/using-data-feeds) page to learn more. Using Solidity, your smart contract should reference [`AggregatorV3Interface`](https://github.com/smartcontractkit/chainlink/blob/contracts-v1.3.0/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol), which defines the external functions implemented by Data Feeds. Example for reading a Proof of Reserve feed: ```sol // SPDX-License-Identifier: MIT pragma solidity 0.8.24; import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol"; contract ReserveConsumerV3 { AggregatorV3Interface internal reserveFeed; /** * Network: Ethereum Mainnet * Aggregator: WBTC PoR * Address: 0xa81FE04086865e63E12dD3776978E49DEEa2ea4e */ constructor() { reserveFeed = AggregatorV3Interface(0xa81FE04086865e63E12dD3776978E49DEEa2ea4e); } /** * Returns the latest price */ function getLatestReserve() public view returns (int256) { // prettier-ignore ( /*uint80 roundID*/ , int256 reserve, /*uint startedAt*/ , /*uint timeStamp*/ , /*uint80 answeredInRound*/ ) = reserveFeed.latestRoundData(); return reserve; } } ``` ### Using MVR Feeds [MVR feeds](/data-feeds/mvr-feeds) require a different approach compared to single-value SmartData feeds. Instead of returning a single numeric value, they return a bytes array that must be decoded into a specific data structure. Your code needs to: 1. Call the `latestBundle()` function to get the raw data 2. Decode the bytes array into a struct that matches the feed's structure 3. Apply the appropriate decimal scaling to numeric fields For detailed implementation guides, see: - [Using MVR Feeds on EVM Chains (Solidity)](/data-feeds/mvr-feeds/guides/evm-solidity) - [Using MVR Feeds with ethers.js (JS)](/data-feeds/mvr-feeds/guides/ethersjs) - [Using MVR Feeds with Viem (TS)](/data-feeds/mvr-feeds/guides/viem) --- # Data Feeds on Solana Source: https://docs.chain.link/data-feeds/solana Chainlink provides data feeds on the [Solana](https://solana.com/) network. Chainlink data feeds on Solana employ [Offchain Reporting (OCR)](/architecture-overview/off-chain-reporting?parent=dataFeeds) to aggregate data from data providers who pull from both centralized and decentralized exchanges. Chainlink's Solana deployment has no dependencies on external blockchain networks such as Ethereum. In Solana, storage and smart contract logic are separate. Programs store all the logic similar to an EVM (Ethereum) smart contract. The accounts store all the data. Compared to Solidity, the combination of an account and a program is equivalent to a smart contract on an EVM chain. State and logic are separate in Solana. Solana programs are stateless, so you don't always need to deploy your program to the network to test it. You can deploy and test your programs on a [Solana Test Validator](https://docs.solana.com/developing/test-validator). However, to use Chainlink products on Solana, you must deploy your contract onchain to one of the [supported Solana clusters](#chainlink-products-and-solana-clusters). > **NOTE: Note** > > Please note that Price Feeds performance relies on the chains they are deployed on. Periods of high network congestion > may impact the frequency of Chainlink Price Feeds. Subscribe to [Solana status](https://status.solana.com/) > notifications to stay updated on system performance. To learn how to mitigate risk to your applications, read the [Selecting Quality Data Feeds](/data-feeds/selecting-data-feeds) page. ## Chainlink products and Solana clusters [Price Feeds](/data-feeds/price-feeds/addresses?network=solana) are available on the following Solana clusters: - [Solana Mainnet](https://solscan.io/) - [Solana Devnet](https://solscan.io/?cluster=devnet) Solana provides a [Testnet cluster](https://docs.solana.com/clusters#testnet) that runs newer [Solana releases](https://github.com/solana-labs/solana/releases), but Chainlink Data Feeds are not available on this cluster. See the [Solana Data Feeds](/data-feeds/price-feeds/addresses?network=solana) page for a full list of Chainlink data feeds that are available on Solana. To learn when more Chainlink services become available, follow us on [Twitter](https://twitter.com/chainlink) or sign up for our [mailing list](/resources/developer-communications?parent=dataFeeds). ## Languages, tools, and frameworks The examples in the Chainlink documentation use the following languages, tools, and frameworks: - [Node.js 14 or higher](https://nodejs.org/en/download/): Used to run client code - [Rust](https://www.rust-lang.org/): A general-purpose programming language designed for performance and memory safety - [Anchor](https://www.anchor-lang.com/): A [Solana Sealevel Framework](https://github.com/coral-xyz/anchor) that provides several developer tools - [Chainlink Solana Starter Kit](https://github.com/smartcontractkit/solana-starter-kit): An Anchor based program and client that shows developers how to use and interact with Chainlink Data Feeds on Solana - [Solana CLI](https://docs.solana.com/cli): The Solana command line interface - [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git): Used to clone the example code repository When developing applications to use Chainlink products on Solana, always use a [Mainnet release](https://github.com/solana-labs/solana/releases) version of the Solana CLI that is equal to or greater than the version currently running on your target cluster. Use `solana --version` and `solana cluster-version` to check CLI and cluster versions: ```shell solana --version solana-cli 1.9.28 (src:b576e9cc; feat:320703611) solana cluster-version --url devnet 1.9.25 solana cluster-version --url mainnet-beta 1.9.28 ``` The examples in this documentation use Solana programs in [Rust](https://solana.com/docs/programs/rust). To learn more about the Solana programming model, see the [Solana Documentation](https://solana.com/docs/core). ## Solana wallets When you use Chainlink on Solana, you need a [Solana wallet](https://docs.solana.com/wallet-guide/). The Chainlink documentation uses [file system wallets](https://docs.solana.com/wallet-guide/file-system-wallet) and free Devnet SOL tokens to demonstrate examples. When you deploy your programs to the Solana Mainnet, you must use wallets with mainnet lamports. If you have existing wallets that you want to use for the guides in the Chainlink documentation, find your wallet keypair and make it available in your development environment as a file. You can point [Anchor](https://www.anchor-lang.com) and the [Solana CLI](https://docs.solana.com/cli) to a specific keypair when you deploy or manage your Solana programs. ```shell anchor build ⋮ anchor deploy --provider.wallet ~/.config/solana/id.json --provider.cluster devnet ⋮ solana program show --programs --keypair ~/.config/solana/id.json --url devnet Program Id | Slot | Authority | Balance 6U4suTp55kiJRKqV7HGAQvFgcLaStLnUA4myg5DRqsKw | 109609728 | E6gKKToCJPgf4zEL1GRLL6T99g2WcfAzJAMvtma1KijT | 2.57751768 SOL ``` When you build your production applications and deploy Solana programs to the Mainnet cluster, always follow the security best practices in the [Solana Wallet Guide](https://docs.solana.com/wallet-guide) for managing your wallets and keypairs. --- # Using Data Feeds Offchain (Solana) Source: https://docs.chain.link/data-feeds/solana/using-data-feeds-off-chain Chainlink Data Feeds are the quickest way to access market prices for real-world assets. This guide demonstrates how to read Chainlink Data Feeds on the Solana Devnet using offchain examples in the [Chainlink Solana Starter Kit](https://github.com/smartcontractkit/solana-starter-kit). To learn how to use Data Feeds in your onchain Solana programs, see the [Using Data Feeds onchain](/data-feeds/solana/using-data-feeds-solana) guide. To get the full list of Chainlink Data Feeds on Solana, see the [Solana Feeds](/data-feeds/price-feeds/addresses?network=solana) page. > **DANGER: Select quality data feeds** > > Be aware of the quality of the data that you use. [Learn more about making responsible data quality decisions](/data-feeds/selecting-data-feeds). ## The Chainlink Data Feeds Store Program The program that contains the logic required for the storing and retrieval of Chainlink Data Feeds data on both Devnet and Mainnet is [cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ](https://solscan.io/account/cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ?cluster=devnet). This is the program ID that you use to read price data from offchain. You can find the source code for this program in the [smartcontractkit/chainlink-solana](https://github.com/smartcontractkit/chainlink-solana/tree/develop/contracts/programs/store/src) on GitHub. You can [add data feeds to an existing offchain project](#adding-data-feeds-to-an-existing-offchain-project) or [use the Solana Starter Kit](#using-the-solana-starter-kit). ## Adding Data Feeds to an existing offchain project You can read Chainlink Data Feeds offchain in your existing project by using the [Chainlink Solana NPM library](https://www.npmjs.com/package/@chainlink/solana-sdk). > **CAUTION: Reading feed data** > > Although you can directly query the data feed accounts, you should not rely on the memory layout always being the same as it currently is. Based on this, the recommendation is to always use the consumer library. Install the necessary components and include the example code in your project. Optionally, you can run the example code by itself to learn how it works before you integrate it with your project. 1. Install the latest Mainnet version of [the Solana CLI](https://github.com/solana-labs/solana/releases) and export the path to the CLI: ```shell sh -c "$(curl -sSfL https://release.solana.com/v1.13.6/install)" && export PATH="~/.local/share/solana/install/active_release/bin:$PATH" ``` Run `solana --version` to make sure the Solana CLI is installed correctly. ```shell solana --version ``` 2. Install [Node.js 14 or higher](https://nodejs.org/en/download/). Run `node --version` to verify which version you have installed: ```shell node --version ``` 3. Change to your project directory or create a new directory. ```shell mkdir off-chain-project && cd off-chain-project ``` 4. Optionally [install Yarn](https://classic.yarnpkg.com/lang/en/docs/install/) to use as a package manager and initialize yarn if your project does not already have a `package.json` file: ```shell npm install -g yarn && yarn init ``` 5. Add the [Anchor library](https://www.npmjs.com/package/@project-serum/anchor) to your project: ### npm ```shell npm npm i @project-serum/anchor ``` ### yarn ```shell yarn yarn add @project-serum/anchor ``` 6. Add the [Chainlink Solana NPM library](https://www.npmjs.com/package/@chainlink/solana-sdk) to your project: ### npm ```shell npm npm i -g @chainlink/solana-sdk ``` ### yarn ```shell yarn yarn add @chainlink/solana-sdk ``` 7. [Create a temporary Solana wallet](https://solana.com/docs/intro/installation#create-wallet) to use for this example. Alternatively, if you have an existing wallet that you want to use, locate the path to your keypair file and use it as the keypair for the rest of this guide. ```shell solana-keygen new --outfile ./id.json ``` 8. Set the [Anchor environment variables](https://www.twilio.com/en-us/blog/how-to-set-environment-variables-html). Anchor uses these to determine which wallet to use and how to get a connection to a Solana cluster. Because this example does not generate or sign any transactions, no lamports are required. The wallet is required only by the Anchor library. For a list of available networks and endpoints, see the [Solana Cluster RPC Endpoints](https://docs.solana.com/cluster/rpc-endpoints) documentation. ```shell export ANCHOR_PROVIDER_URL=https://api.devnet.solana.com && export ANCHOR_WALLET=./id.json ``` 9. Copy the sample code into your project. This example queries price data offchain. By default, the script reads the SOL/USD feed, but you can change the `CHAINLINK_FEED_ADDRESS` variable to point to the [feed account addresses](/data-feeds/price-feeds/addresses?network=solana) that you want to query. You can take the components of these code samples and integrate them with your existing project. Because these examples read data feeds without making any onchain changes, no lamports are required to run them. ```js /** * THIS IS EXAMPLE CODE THAT USES HARDCODED VALUES FOR CLARITY. * THIS IS EXAMPLE CODE THAT USES UN-AUDITED CODE. * DO NOT USE THIS CODE IN PRODUCTION. */ const anchor = require("@project-serum/anchor") const chainlink = require("@chainlink/solana-sdk") const provider = anchor.AnchorProvider.env() async function main() { anchor.setProvider(provider) const CHAINLINK_FEED_ADDRESS = "99B2bTijsU6f1GCT73HmdR7HCFFjGMBcPZY6jZ96ynrR" const CHAINLINK_PROGRAM_ID = new anchor.web3.PublicKey("cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ") const feedAddress = new anchor.web3.PublicKey(CHAINLINK_FEED_ADDRESS) //SOL-USD Devnet Feed //load the data feed account let dataFeed = await chainlink.OCR2Feed.load(CHAINLINK_PROGRAM_ID, provider) let listener = null //listen for events agains the price feed, and grab the latest rounds price data listener = dataFeed.onRound(feedAddress, (event) => { console.log(event.answer.toNumber()) }) //block execution and keep waiting for events to be emitted with price data await new Promise(function () {}) } main().then( () => process.exit(), (err) => { console.error(err) process.exit(-1) } ) ``` ```ts /** * THIS IS EXAMPLE CODE THAT USES HARDCODED VALUES FOR CLARITY. * THIS IS EXAMPLE CODE THAT USES UN-AUDITED CODE. * DO NOT USE THIS CODE IN PRODUCTION. */ import * as anchor from "@project-serum/anchor" import { OCR2Feed } from "@chainlink/solana-sdk" async function main() { const provider = anchor.AnchorProvider.env() anchor.setProvider(provider) const CHAINLINK_FEED_ADDRESS = "99B2bTijsU6f1GCT73HmdR7HCFFjGMBcPZY6jZ96ynrR" const CHAINLINK_PROGRAM_ID = new anchor.web3.PublicKey("cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ") const feedAddress = new anchor.web3.PublicKey(CHAINLINK_FEED_ADDRESS) //SOL-USD Devnet Feed //load the data feed account let dataFeed = await OCR2Feed.load(CHAINLINK_PROGRAM_ID, provider) let listener: null | number = null //listen for events agains the price feed, and grab the latest rounds price data listener = dataFeed.onRound(feedAddress, (event) => { console.log(event.answer.toNumber()) }) //block execution and keep waiting for events to be emitted with price data await new Promise(function () {}) } main().then( () => process.exit(), (err) => { console.error(err) process.exit(-1) } ) ``` You can run these examples using the following commands: ### Javascript ```shell javascript node javascript-example.js ``` ### Typescript ```shell typescript yarn add ts-node typescript && yarn ts-node typescript-example.ts ``` To learn more about Solana and Anchor, see the [Solana Documentation](https://docs.solana.com/) and the [Anchor Documentation](https://book.anchor-lang.com/). ## Using the Solana Starter Kit This example reads price data from an offchain client using the [Solana Starter Kit](https://github.com/smartcontractkit/solana-starter-kit). ### Install the required tools Before you begin, set up your environment for development on Solana: 1. Install [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) if it is not already configured on your system. 2. Install the latest Mainnet version of [the Solana CLI](https://github.com/solana-labs/solana/releases) and export the path to the CLI: ```shell sh -c "$(curl -sSfL https://release.solana.com/v1.13.6/install)" && export PATH="~/.local/share/solana/install/active_release/bin:$PATH" ``` Run `solana --version` to make sure the Solana CLI is installed correctly. ```shell solana --version ``` 3. Install [Node.js 14 or higher](https://nodejs.org/en/download/). Run `node --version` to verify which version you have installed: ```shell node --version ``` 4. [Install Anchor](https://www.anchor-lang.com/docs/installation). On some operating systems, you might need to build and install Anchor locally. See the [Anchor documentation](https://www.anchor-lang.com/docs) for instructions. 5. Install [Yarn](https://classic.yarnpkg.com/lang/en/docs/install/) to simplify package management and run code samples in the Starter Kit. ```shell npm install -g yarn ``` ### Run the example program After you install the required tools, clone the example code from the [solana-starter-kit](https://github.com/smartcontractkit/solana-starter-kit) repository. 1. In a terminal, clone the [solana-starter-kit](https://github.com/smartcontractkit/solana-starter-kit) repository and change to the `solana-starter-kit` directory: ```shell git clone https://github.com/smartcontractkit/solana-starter-kit && cd ./solana-starter-kit ``` You can see the complete code for the example on [GitHub](https://github.com/smartcontractkit/solana-starter-kit/). 2. In the `./solana-starter-kit` directory, install Node.js dependencies defined in the `package.json` file: ```shell yarn install ``` 3. Create a temporary Solana wallet file to use for this example. Because your application runs offchain and does not run any functions or alter data onchain, the wallet does not require any SOL tokens to function. ```shell solana-keygen new --outfile ./id.json ``` 4. Set the [Anchor environment variables](https://www.twilio.com/en-us/blog/how-to-set-environment-variables-html). Anchor uses these to determine which wallet to use and Solana cluster to use. Take note that because we are not generating or signing any transactions, the wallet isn't used, it's just required by the Anchor library. For a list of available networks and endpoints, see the [Solana Cluster RPC Endpoints](https://docs.solana.com/cluster/rpc-endpoints) documentation. ```shell Solana Devnet export ANCHOR_PROVIDER_URL=https://api.devnet.solana.com && export ANCHOR_WALLET=./id.json ``` 5. Run the example: ### Javascript ```shell JavaScript node read-data.js ``` ### Typescript ```shell TypeScript yarn run read-data ``` The example code retrieves and prints the current price feed data until you close the application: ``` 4027000000 4026439929 4026476542 4023000000 ``` To learn more about Solana and Anchor, see the [Solana Documentation](https://docs.solana.com/) and the [Anchor Documentation](https://book.anchor-lang.com/). --- # Using Data Feeds Onchain (Solana) Source: https://docs.chain.link/data-feeds/solana/using-data-feeds-solana > **NOTE: SDK v2 is now available** > > The Chainlink [Solana SDK v2 is now available](https://crates.io/crates/chainlink_solana/), offering improved performance with lower compute unit usage through direct account reads instead of Cross-Program Invocation (CPI). This guide demonstrates the v2 SDK pattern. If you're using the v1 SDK, see the [migration guide below](#migrating-from-v1-to-v2) to upgrade. Chainlink Data Feeds are the quickest way to connect your smart contracts to the real-world market prices of assets. This guide demonstrates how to deploy a program to the Solana Devnet cluster and access Data Feeds onchain using the [Chainlink Solana Starter Kit](https://github.com/smartcontractkit/solana-starter-kit). To learn how to read price feed data using offchain applications, see the [Using Data Feeds Offchain](/solana/using-data-feeds-off-chain) guide. To get the full list of available Chainlink Data Feeds on Solana, see the [Solana Feeds](/data-feeds/price-feeds/addresses?network=solana) page. View the program that owns the Chainlink Data Feeds in the [Solana Devnet Explorer](https://solscan.io/account/HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny?cluster=devnet), or the [Solana Mainnet Explorer](https://solscan.io/account/HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny). > **DANGER: Select quality data feeds** > > Be aware of the quality of the data that you use. [Learn more about making responsible data quality decisions](/data-feeds/selecting-data-feeds). ## The Chainlink Data Feeds OCR2 program The program that owns the data feeds on both Devnet and Mainnet is [HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny](https://solscan.io/account/HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny?cluster=devnet). This is the program ID that you use to retrieve Chainlink Price Data onchain in your program. The source code for this program is available in the [smartcontractkit/chainlink-solana](https://github.com/smartcontractkit/chainlink-solana/tree/develop/contracts/programs/ocr_2) repository on GitHub. You can [add data feeds to an existing project](#adding-data-feeds-onchain-in-an-existing-project) or [use the Solana Starter Kit](#using-the-solana-starter-kit). ## Adding Data Feeds onchain in an existing project You can read Chainlink Data Feed data onchain in your existing project using the [Chainlink Solana Crate](https://crates.io/crates/chainlink_solana). SDK v2 uses direct account reads for improved performance and lower compute unit usage compared to the deprecated v1 SDK which used Cross-Program Invocation (CPI). > **CAUTION: Reading feed data** > > Although you can directly query the data feed accounts, you should not rely on the memory layout always being the same as it currently is. Based on this, the recommendation is to always use the consumer library queries below. Import the Chainlink Solana Crate into your project and use the code sample to make function calls. 1. Add the Chainlink Solana Crate as an entry in your `Cargo.toml` file dependencies section, as shown in the [starter kit Cargo.toml example](https://github.com/smartcontractkit/solana-starter-kit/blob/main/programs/chainlink_solana_demo/Cargo.toml). ```toml [dependencies] chainlink_solana = "2.0.8" ``` If you're using [Anchor](https://www.anchor-lang.com/docs), also add: ```toml [dependencies] anchor-lang = "0.31.1" chainlink_solana = "2.0.8" ``` 2. Choose the code sample that matches your project setup: - **Rust (Anchor)** - **Rust (Vanilla)** Both samples demonstrate SDK v2, which reads data directly from the feed account. The code samples have the following components: - `read_feed_v2`: Reads feed data directly from the account data with the feed owner verification - `latest_round_data`: Returns the latest round information for the specified price pair including the latest price - `description`: Returns a price pair description such as SOL/USD - `decimals`: Returns the precision of the price, as in how many numbers the price is padded out to - `Display`: A helper function that formats the padded out price data into a human-readable price ### Rust (Anchor) ```rs /** * THIS IS EXAMPLE CODE THAT USES HARDCODED VALUES FOR CLARITY. * THIS IS EXAMPLE CODE THAT USES UN-AUDITED CODE. * DO NOT USE THIS CODE IN PRODUCTION. */ use anchor_lang::prelude::*; use chainlink_solana::v2::read_feed_v2; //Program ID required by Anchor. Replace with your unique program ID once you build your project declare_id!("HPuUpM1bKbaqx7yY2EJ4hGBaA3QsfP5cofHHK99daz85"); #[account] pub struct Decimal { pub value: i128, pub decimals: u32, } impl Decimal { pub fn new(value: i128, decimals: u32) -> Self { Decimal { value, decimals } } } impl std::fmt::Display for Decimal { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut scaled_val = self.value.to_string(); if scaled_val.len() <= self.decimals as usize { scaled_val.insert_str( 0, &vec!["0"; self.decimals as usize - scaled_val.len()].join(""), ); scaled_val.insert_str(0, "0."); } else { scaled_val.insert(scaled_val.len() - self.decimals as usize, '.'); } f.write_str(&scaled_val) } } #[program] pub mod chainlink_solana_demo { use super::*; pub fn execute(ctx: Context) -> Result<()> { let feed = &ctx.accounts.chainlink_feed; // Read the feed data directly from the account (v2 SDK) let result = read_feed_v2( feed.try_borrow_data()?, feed.owner.to_bytes(), ) .map_err(|_| DemoError::ReadError)?; // Get the latest round data let round = result .latest_round_data() .ok_or(DemoError::RoundDataMissing)?; let description = result.description(); let decimals = result.decimals(); // Convert description bytes to string let description_str = std::str::from_utf8(&description) .unwrap_or("Unknown") .trim_end_matches('\0'); // write the latest price to the program output let decimal_print = Decimal::new(round.answer, u32::from(decimals)); msg!("{} price is {}", description_str, decimal_print); Ok(()) } } #[derive(Accounts)] pub struct Execute<'info> { /// CHECK: We're reading data from this chainlink feed account pub chainlink_feed: AccountInfo<'info>, } #[error_code] pub enum DemoError { #[msg("read error")] ReadError, #[msg("no round data")] RoundDataMissing, } ``` ### Rust (Vanilla) ```rs /** * THIS IS EXAMPLE CODE THAT USES HARDCODED VALUES FOR CLARITY. * THIS IS EXAMPLE CODE THAT USES UN-AUDITED CODE. * DO NOT USE THIS CODE IN PRODUCTION. */ use chainlink_solana::v2::read_feed_v2; use solana_program::{ account_info::{next_account_info, AccountInfo}, entrypoint, entrypoint::ProgramResult, msg, pubkey::Pubkey, }; struct Decimal { pub value: i128, pub decimals: u32, } impl Decimal { pub fn new(value: i128, decimals: u32) -> Self { Decimal { value, decimals } } } impl std::fmt::Display for Decimal { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut scaled_val = self.value.to_string(); if scaled_val.len() <= self.decimals as usize { scaled_val.insert_str( 0, &vec!["0"; self.decimals as usize - scaled_val.len()].join(""), ); scaled_val.insert_str(0, "0."); } else { scaled_val.insert(scaled_val.len() - self.decimals as usize, '.'); } f.write_str(&scaled_val) } } // Declare and export the program's entrypoint entrypoint!(process_instruction); // Program entrypoint's implementation pub fn process_instruction( _program_id: &Pubkey, // Ignored accounts: &[AccountInfo], _instruction_data: &[u8], // Ignored ) -> ProgramResult { msg!("Chainlink Price Feed Consumer entrypoint"); let accounts_iter = &mut accounts.iter(); // This is the account of the price feed data to read from let feed_account = next_account_info(accounts_iter)?; // Read the feed data directly from the account (v2 SDK) let result = read_feed_v2( feed_account.try_borrow_data()?, feed_account.owner.to_bytes(), ) .map_err(|_| solana_program::program_error::ProgramError::InvalidAccountData)?; // Get the latest round data let round = result .latest_round_data() .ok_or(solana_program::program_error::ProgramError::InvalidAccountData)?; let description = result.description(); let decimals = result.decimals(); // Convert description bytes to string let description_str = std::str::from_utf8(&description) .unwrap_or("Unknown") .trim_end_matches('\0'); let decimal_print = Decimal::new(round.answer, u32::from(decimals)); msg!("{} price is {}", description_str, decimal_print); Ok(()) } ``` Program Transaction logs: ### Rust (Anchor) ```shell Rust with Anchor Fetching transaction logs... [ 'Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny consumed 1826 of 1306895 compute units', 'Program return: HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny CA==', 'Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny success', 'Program log: SOL / USD price is 93.76988029', ] ``` ### Rust (Vanilla) ```shell Rust > Program logged: "Chainlink Price Feed Consumer entrypoint" > Program logged: "SOL / USD price is 83.99000000" > Program consumed: 95953 of 1400000 compute units > Program return: HNYSbr77Jc9LhHeb9tx53SrWbWfNBnQzQrM4b3BB3PCR CA== ``` To learn more about Solana and Anchor, see the [Solana Documentation](https://docs.solana.com/) and the [Anchor Documentation](https://www.anchor-lang.com/). ## Using the Solana starter kit This guide demonstrates the following tasks: - Write and deploy programs to the [Solana Devnet](https://solscan.io/?cluster=devnet) cluster using Anchor. - Retrieve price data data using the [Solana Web3 JavaScript API](https://www.npmjs.com/package/@solana/web3.js) with Node.js. This example shows a full end to end example of using Chainlink Price Feeds on Solana. It includes an onchain program written in rust, as well as an offchain client written in JavaScript. The client passes in an account to the program, the program then looks up the latest price of the specified price feed account, and then stores the result in the passed in account. The offchain client then reads the value stored in the account. ### Install the required tools Before you begin, set up your environment for development on Solana: 1. Install [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) if it is not already configured on your system. 2. Install [Node.js 14 or higher](https://nodejs.org/en/download/). Run `node --version` to verify which version you have installed: ```shell node --version ``` 3. Install [Yarn](https://classic.yarnpkg.com/lang/en/docs/install/) to simplify package management and run code samples. 4. Install a C compiler such as the one included in [GCC](https://gcc.gnu.org/install/). Some of the dependencies require a C compiler. 5. Install [Rust](https://www.rust-lang.org/tools/install): ```shell curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh && source $HOME/.cargo/env ``` 6. Install the latest Mainnet version of [the Solana CLI](https://github.com/solana-labs/solana/releases) and export the path to the CLI: ```shell sh -c "$(curl -sSfL https://release.solana.com/v1.13.6/install)" && export PATH="~/.local/share/solana/install/active_release/bin:$PATH" ``` Run `solana --version` to make sure the Solana CLI is installed correctly. ```shell solana --version ``` 7. [Install Anchor](https://www.anchor-lang.com/docs/installation). On some operating systems, you might need to build and install Anchor locally. See the [Anchor documentation](https://www.anchor-lang.com/docs) for instructions. After you install the required tools, build and deploy the example program from the [solana-starter-kit](https://github.com/smartcontractkit/solana-starter-kit) repository. ### Deploy the example program This example includes a contract written in Rust. Deploy the contract to the Solana Devnet cluster. 1. In a terminal, clone the [solana-starter-kit](https://github.com/smartcontractkit/solana-starter-kit) repository and change to the `solana-starter-kit` directory: ```shell git clone https://github.com/smartcontractkit/solana-starter-kit && cd ./solana-starter-kit ``` You can see the complete code for the example on [GitHub](https://github.com/smartcontractkit/solana-starter-kit/). 2. In the `./solana-starter-kit` directory, install Node.js dependencies defined in the `package.json` file: ```shell yarn install ``` 3. Create a temporary Solana wallet to use for this example. Use a temporary wallet to isolate development from your other wallets and prevent you from unintentionally using lamports on the Solana Mainnet. Alternatively, if you have an existing wallet that you want to use, locate the path to your keypair file and use it as the keypair for the rest of this guide. ```shell solana-keygen new --outfile ./id.json ``` When you build your production applications and deploy Solana programs to the Mainnet cluster, always follow the security best practices in the [Solana Wallet Guide](https://docs.solana.com/wallet-guide) for managing your wallets and keypairs. 4. Fund your Solana wallet. On Devnet, use `solana airdrop` to add tokens to your account. The contract requires at least 4 SOL to deploy and the faucet limits each request to 2 SOL, so you must make two requests to get a total of 4 SOL on your wallet: ```shell solana airdrop 2 --keypair ./id.json --url devnet && solana airdrop 2 --keypair ./id.json --url devnet ``` - If the command line faucet does not work, run `solana address` on the temporary wallet to print the public key value for the wallet and request tokens from [SolFaucet](https://solfaucet.com/): ```shell solana address -k ./id.json ``` 5. Run `anchor build` to build the example program. If you receive the `no such subcommand: 'build-bpf'` error, restart your terminal session and run `anchor build` again: ```shell anchor build ``` 6. The build process generates keypairs for your program accounts. Before you deploy your programs, you must add these public keys to the respective `lib.rs` files. The starter kit contains two programs that both need their IDs updated: 1. **For the `chainlink_solana_demo` program:** Get the keypair from the `./target/deploy/chainlink_solana_demo-keypair.json` file that Anchor generated: ```shell solana address -k ./target/deploy/chainlink_solana_demo-keypair.json ``` Edit the `./programs/chainlink_solana_demo/src/lib.rs` file and replace the keypair in the `declare_id!()` definition with the output from the previous command: ```shell vi ./programs/chainlink_solana_demo/src/lib.rs ``` Example (replace with your actual program ID): ```rust declare_id!("JC16qi56dgcLoaTVe4BvnCoDL6FhH5NtahA7jmWZFdqm"); ``` 2. **For the `ccip_basic_receiver` program:** Get the keypair from the `./target/deploy/ccip_basic_receiver-keypair.json` file: ```shell solana address -k ./target/deploy/ccip_basic_receiver-keypair.json ``` Edit the `./programs/ccip-basic-receiver/src/lib.rs` file and replace the keypair in the `declare_id!()` definition with the output from the previous command: ```shell vi ./programs/ccip-basic-receiver/src/lib.rs ``` Example (replace with your actual program ID): ```rust declare_id!("ANvQpnDMhuuKmSWYV2ET7A78UZeU2bWbGjRpn4z27nv2"); ``` 7. With the new program IDs added to both files, run `anchor build` again. This recreates the necessary program files with the correct program IDs: ```shell anchor build ``` 8. Run `anchor deploy` to deploy the program to the Solana Devnet. Remember to specify the keypair file for your wallet and override the default. This wallet is the account owner (authority) for the program: ```shell anchor deploy --provider.wallet ./id.json --provider.cluster devnet ``` 9. To confirm that the program deployed correctly, run `solana program show --programs` to get a list of deployed programs that your wallet owns. For this example, check the list of deployed programs for the `id.json` wallet on the Solana Devnet: ```shell solana program show --programs --keypair ./id.json --url devnet ``` The command prints information for both deployed programs, including the program ID, slot number, the wallet address that owns each program, and the program balance: ```shell Program Id | Slot | Authority | Balance ANvQpnDMhuuKmSWYV2ET7A78UZeU2bWbGjRpn4z27nv2 | 110801571 | FsQPnANKDhqpoayxCL3oDHFCBmrhP34NrfbDR34qbQUt | 2.23 SOL JC16qi56dgcLoaTVe4BvnCoDL6FhH5NtahA7jmWZFdqm | 110801572 | FsQPnANKDhqpoayxCL3oDHFCBmrhP34NrfbDR34qbQUt | 1.47 SOL ``` To see additional details of your deployed programs, copy a program ID and look it up in the [Solana Devnet Explorer](https://solscan.io/?cluster=devnet). Now that the program is onchain, you can call it. ### Call the deployed program Use your deployed program to retrieve price data from a Chainlink data feed on Solana Devnet. For this example, call your deployed program using the [`client.js` example](https://github.com/smartcontractkit/solana-starter-kit/blob/main/client.js) code. 1. Set the [Anchor environment variables](https://www.twilio.com/en-us/blog/how-to-set-environment-variables-html). Anchor uses these to determine which wallet to use and Solana cluster to use. ```shell export ANCHOR_PROVIDER_URL=https://api.devnet.solana.com && export ANCHOR_WALLET=./id.json ``` 2. Run the `client.js` example and pass the program address in using the `--program` flag: ```shell node client.js --program $(solana address -k ./target/deploy/chainlink_solana_demo-keypair.json) ``` If the script executes correctly, you will see output with the current price of SOL / USD. ```shell ⋮ Price Is: 96.79778375 Success ⋮ ``` 3. Each request costs an amount of SOL that is subtracted from the `id.json` wallet. Run `solana balance` to check the remaining balance for your temporary wallet on Devnet. ```shell solana balance --keypair ./id.json --url devnet ``` 4. To get prices for a different asset pair, run `client.js` again and add the `--feed` flag with one of the available [Chainlink data feeds](/data-feeds/price-feeds/addresses?network=solana). For example, to get the price of BTC / USD on Devnet, use the following command: ```shell node client.js \ --program $(solana address -k ./target/deploy/chainlink_solana_demo-keypair.json) \ --feed 6PxBx93S8x3tno1TsFZwT5VqP8drrRCbCXygEXYNkFJe ``` ```shell Price Is: 111198.0536333 Success ``` The program that owns the data feeds is [HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny](https://solscan.io/account/HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny?cluster=devnet), which you can see defined for `const CHAINLINK_PROGRAM_ID` in the `client.js` file. ### Clean up After you are done with your deployed contract and no longer need it, it is nice to close the program and withdraw the Devnet SOL tokens for future use. In a production environment, you will want to withdraw unused SOL tokens from any Solana program that you no longer plan to use, so it is good to practice the process when you are done with programs on Devnet. 1. Run `solana program show` to see the list of deployed programs that your wallet owns and the balances for each of those programs: ```shell solana program show --programs --keypair ./id.json --url devnet ``` ```shell Program Id | Slot | Authority | Balance GRt21UnJFHZvcaWLbcUrXaTCFMREewDrm1DweDYBak3Z | 110801571 | FsQPnANKDhqpoayxCL3oDHFCBmrhP34NrfbDR34qbQUt | 3.07874904 SOL ``` 2. Run `solana program close` and specify the program that you want to close: ```shell solana program close [YOUR_PROGRAM_ID] --keypair ./id.json --url devnet ``` The program closes and the remaining SOL is transferred to your temporary wallet. 3. If you have deployments that failed, they might still be in the buffer holding SOL tokens. Run `solana program show` again with the `--buffers` flag: ```shell solana program show --buffers --keypair ./id.json --url devnet ``` If you have open buffers, they will appear in the list. ```shell Buffer Address | Authority | Balance CSc9hnBqYJoYtBgsryJAmrjAE6vZ918qaFhL6N6BdEmB | FsQPnANKDhqpoayxCL3oDHFCBmrhP34NrfbDR34qbQUt | 1.28936088 SOL ``` 4. If you have any buffers that you do not plan to finish deploying, run the same `solana program close` command to close them and retrieve the unused SOL tokens: ```shell solana program close [YOUR_PROGRAM_ID] --keypair ./id.json --url devnet ``` 5. Check the balance on your temporary wallet. ```shell solana balance --keypair ./id.json --url devnet ``` 6. If you are done using this wallet for examples and testing, you can use [`solana transfer`](https://docs.solana.com/cli/transfer-tokens) to send the remaining SOL tokens to your default wallet or another Solana wallet that you use. For example, if your default wallet keypair is at `~/.config/solana/id.json`, you can send `ALL` of the temporary wallet's balance with the following command: ```shell solana transfer ~/.config/solana/id.json ALL --keypair ./id.json --url devnet ``` Alternatively, you can send the remaining balance to a web wallet. Specify the public key for your wallet instead of the path the default wallet keypair. Now you can use those Devnet funds for other examples and development. To learn more about Solana and Anchor, see the [Solana Documentation](https://docs.solana.com/) and the [Anchor Documentation](https://www.anchor-lang.com/). ## Migrating from v1 to v2 SDK v2 uses direct account reads instead of Cross-Program Invocation (CPI), resulting in better performance, lower compute units, and simpler code. ### Key changes 1. Update dependency in `Cargo.toml`: ```toml chainlink_solana = "2.0.8" # was "1.0.0" ``` 2. Update imports: ```rust use chainlink_solana::v2::read_feed_v2; // was: use chainlink_solana as chainlink; ``` 3. Remove `chainlink_program` from account context - only `chainlink_feed` is needed 4. Replace multiple CPI calls with a single read: ### v1 Pattern ```rust // Multiple CPI calls let round = chainlink::latest_round_data( ctx.accounts.chainlink_program.to_account_info(), ctx.accounts.chainlink_feed.to_account_info(), )?; let description = chainlink::description(/* ... */)?; let decimals = chainlink::decimals(/* ... */)?; ``` ### v2 Pattern ```rust // Single read operation let feed = &ctx.accounts.chainlink_feed; let result = read_feed_v2( feed.try_borrow_data()?, feed.owner.to_bytes(), ).map_err(|_| MyError::ReadError)?; let round = result.latest_round_data() .ok_or(MyError::RoundDataMissing)?; let description = result.description(); let decimals = result.decimals(); ``` 5. Add error types (v2 requires explicit error handling): ```rust #[error_code] pub enum MyError { #[msg("read error")] ReadError, #[msg("no round data")] RoundDataMissing, } ``` 6. Update client code - remove `chainlinkProgram` from transaction accounts: ```typescript await program.methods .execute() .accounts({ chainlinkFeed: CHAINLINK_FEED }) // Remove chainlinkProgram .rpc() ``` ### Complete example ### v1 (Deprecated) ```rust use anchor_lang::prelude::*; use chainlink_solana as chainlink; #[program] pub mod my_program { use super::*; pub fn execute(ctx: Context) -> Result<()> { let round = chainlink::latest_round_data( ctx.accounts.chainlink_program.to_account_info(), ctx.accounts.chainlink_feed.to_account_info(), )?; msg!("Price: {}", round.answer); Ok(()) } } #[derive(Accounts)] pub struct Execute<'info> { pub chainlink_feed: AccountInfo<'info>, pub chainlink_program: AccountInfo<'info>, } ``` ### v2 (Recommended) ```rust use anchor_lang::prelude::*; use chainlink_solana::v2::read_feed_v2; #[program] pub mod my_program { use super::*; pub fn execute(ctx: Context) -> Result<()> { let feed = &ctx.accounts.chainlink_feed; let result = read_feed_v2( feed.try_borrow_data()?, feed.owner.to_bytes(), ).map_err(|_| MyError::ReadError)?; let round = result.latest_round_data() .ok_or(MyError::RoundDataMissing)?; msg!("Price: {}", round.answer); Ok(()) } } #[derive(Accounts)] pub struct Execute<'info> { pub chainlink_feed: AccountInfo<'info>, } #[error_code] pub enum MyError { #[msg("read error")] ReadError, #[msg("no round data")] RoundDataMissing, } ``` --- # Using Data Feeds on Starknet Source: https://docs.chain.link/data-feeds/starknet Starknet is a permissionless decentralized Zk-rollup operating as an L2 network over Ethereum. Unlike other Ethereum L2 networks, Starknet is not EVM-compatible and uses [Cairo](https://docs.cairo-lang.org/index.html) as its smart contract language. Chainlink Data Feeds are available on Starknet Sepolia as Cairo smart contracts. ### Offchain examples You can read Chainlink Data Feeds offchain without a Starknet account. Complete these steps using only the [Starkli CLI](#getting-started-starkli-cli) or the [Starknet Foundry](/data-feeds/starknet/tutorials/snfoundry/read-data) toolkit. ### Onchain examples You can read Chainlink Data Feeds on Starknet Sepolia using an [onchain contract](/data-feeds/starknet/tutorials/snfoundry/consumer-contract) that you compile, declare, and deploy using [Starknet Foundry](https://github.com/foundry-rs/starknet-foundry). ### Devnet examples Experiment with Chainlink Data Feeds on Starknet using [Starknet Devnet RS](https://github.com/0xSpaceShard/starknet-devnet-rs), a local Docker-based testnet environment implemented using Rust. The [Starknet Devnet RS guide](/data-feeds/starknet/tutorials/snfoundry/sn-devnet-rs) provides code examples and scripts that enable you to compile, declare, and deploy your own aggregator and consumer contracts. You can use a set of pre-funded accounts to interact with Chainlink Data Feeds on Starknet without deploying to a live network. ## Getting Started (Starkli CLI) [Starkli](https://github.com/xJonathanLEI/starkli) is a Starknet standalone CLI similar to [cairo-lang](https://github.com/starkware-libs/cairo-lang), but it is written in Rust. It enables interaction with a Starknet network without the need for extra tools or dependencies. In this example, you will use the `starkli call` command to read data from a Chainlink Price Feed on Starknet Sepolia. This command requires the proxy aggregator address, the function selector, and an RPC endpoint for Starknet Sepolia. ### Requirements Make sure you have the Starkli CLI installed. You can check your current version by running starkli --version in your terminal. Expect an output similar to the following: ```bash starkli --version 0.2.8 (f59724e) ``` Follow the official [installation guide](https://book.starkli.rs/installation) if necessary. ### Read data from a Chainlink Price Feed Use the following command to call the `latest_round_data` function on the ETH / USD Chainlink Price Feed aggregator proxy: ```bash starkli call \ 0x228128e84cdfc51003505dd5733729e57f7d1f7e54da679474e73db4ecaad44 \ latest_round_data \ --rpc https://starknet-sepolia.public.blastapi.io/rpc/v0_7 ``` Expect an output similar to the following: ```bash [ "0x000000000000000000000000000000010000000000000000000000000000320c", "0x0000000000000000000000000000000000000000000000000000005293770eb1", "0x000000000000000000000000000000000000000000000000000000000000d44d", "0x000000000000000000000000000000000000000000000000000000006606b8cc", "0x000000000000000000000000000000000000000000000000000000006606b8a2" ] ``` The example output contains an array with the hex-encoded latest round data for the ETH / USD Chainlink Price Feed on Starknet Sepolia. The array contains the following values: | Value name | Hex-encoded value | Decoded value | Description | | ------------ | -------------------------------------------------------------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | `round_id` | `0x000000000000000000000000000000010000000000000000000000000000320c` | `340282366920938463463374607431768224268` | The unique identifier of the data round | | `answer` | `0x0000000000000000000000000000000000000000000000000000005293770eb1` | `354661371569` | The actual data provided by the data feed, representing the latest price of an asset in the case of a price feed | | `block_num` | `0x000000000000000000000000000000000000000000000000000000000000d44d` | `54349` | The block number at which the data was recorded on the blockchain | | `started_at` | `0x000000000000000000000000000000000000000000000000000000006606b8cc` | `1711716556` | The Unix timestamp indicating when the data round started | | `updated_at` | `0x000000000000000000000000000000000000000000000000000000006606b8a2` | `1711716514` | The Unix timestamp indicating when the data was last updated | For a complete list of Chainlink Price Feeds available on Starknet, see the [Price Feed Contract Addresses](/data-feeds/price-feeds/addresses?network=starknet) page. Note: This example uses a [Blast API](https://blastapi.io/public-api/starknet) RPC endpoint. You can interact with the network using any other Starknet Sepolia RPC provider, such as [Alchemy](https://www.alchemy.com/starknet) or [Infura](https://www.infura.io/networks/ethereum/starknet). --- # Deploy and Interact with a Consumer Contract using Starknet Foundry Source: https://docs.chain.link/data-feeds/starknet/tutorials/snfoundry/consumer-contract This example uses a local OpenZeppelin account to deploy and interact with a consumer contract onchain. This contract retrieves data from a specified Chainlink data feed on Starknet Sepolia and stores the information for the latest round of data. ## Requirements ### Set up your environment This guide uses the [Starknet Foundry](https://github.com/foundry-rs/starknet-foundry) toolkit and the [Scarb](https://github.com/software-mansion/scarb) project management tool so you can compile, deploy, and interact with your Starknet smart contracts. - **Starknet Foundry**: Install Starknet Foundry **v0.21.0**. You can check your current version by running snforge --version or sncast --version in your terminal and [install the required version](https://github.com/foundry-rs/starknet-foundry#installation) if necessary. - **Scarb**: Install Scarb **v2.6.4**. You can check your current version by running scarb --version in your terminal and [install the required version](https://docs.swmansion.com/scarb/download.html#install-via-installation-script) if necessary. Alternatively, you can use the [asdf](https://asdf-vm.com/guide/introduction.html) tool version manager to install both Starknet Foundry and Scarb. Read the setup instructions for [Starknet Foundry](https://github.com/foundry-rs/asdf-starknet-foundry) and [Scarb](https://docs.swmansion.com/scarb/download.html#install-via-asdf) for more information. ### Clone and configure the code examples repository 1. Clone the [chainlink-starknet](https://github.com/smartcontractkit/chainlink-starknet/) repository, which includes the example contracts for this guide: ```bash git clone https://github.com/smartcontractkit/chainlink-starknet.git ``` 2. Navigate to the `aggregator_consumer` directory: ```bash cd chainlink-starknet/examples/contracts/aggregator_consumer/ ``` After you prepare the requirements, make sure the required tools are configured correctly by running the tests: ```bash make test ``` The contracts should compile successfully and the tests should pass. ## Tutorial ### Create, deploy, and fund an account 1. Run the [`create-account`](https://github.com/smartcontractkit/chainlink-starknet/blob/c97baa6e52c7e6e01ea0235bf0e71abae23e3c00/examples/contracts/aggregator_consumer/Makefile#L22) script to create a new OpenZeppelin local account for the Sepolia testnet: ```bash make create-account ``` The account details are stored locally in your `~/.starknet_accounts/starknet_open_zeppelin_accounts.json` file. Expect an output similar to the following: ```bash command: account create address: 0x3e7ee3d05d3efae4e493c9733c8c391d4a5cc63d34f95a164c832060fcdd43d max_fee: 9529927566560 message: Account successfully created. Prefund generated address with at least tokens. It is good to send more in the case of higher demand. Your accounts: { "alpha-sepolia": { "testnet-account": { "address": "0x3e7ee3d05d3efae4e493c9733c8c391d4a5cc63d34f95a164c832060fcdd43d", "class_hash": "0x4c6d6cf894f8bc96bb9c525e6853e5483177841f7388f74a46cfda6f028c755", "deployed": false, "legacy": false, "private_key": , "public_key": "0x2972c3cb7aa85403fa1e038f9ce7a93f025ea57017eb7ef735bae989bfb15bd", "salt": "0x61a062d2dd4e7656" } } } ``` From the output, note: - Your account address. In this example, it is `0x03e7ee3d05d3efae4e493c9733c8c391d4a5cc63d34f95a164c832060fcdd43d`, with an added zero after `0x`. - The `max_fee` value, which is the minimum amount of testnet ETH required to deploy the account. 2. Fund the newly created account with testnet ETH to cover the account deployment, the consumer contract deployment, and the network interaction costs. Go to the Blast [Starknet Sepolia ETH Faucet](https://blastapi.io/faucets/starknet-sepolia-eth) and enter your account address to receive testnet ETH. Wait a few seconds for the transaction to complete. Alternatively, you can transfer ETH tokens from a Starknet-compatible wallet, such as [Argent](https://www.argent.xyz/) or [Braavos](https://braavos.app/), or use the [StarkGate bridge](https://starkgate.starknet.io/) to transfer testnet ETH from Ethereum Sepolia to Starknet Sepolia. If you need testnet ETH on Ethereum Sepolia, you can use the [Chainlink faucet](https://faucets.chain.link/sepolia). 3. Deploy your account to Starknet Sepolia: ```bash make deploy-account ``` Expect an output similar to the following: ```bash command: account deploy transaction_hash: 0x541ffae49f77dd942376391286052d9fb87dc8d6848139ab1508c114e3aa005 ``` Wait a few seconds for the transaction to complete. ### Deploy and interact with a consumer contract The consumer contract contains two functions that you interact with in this example: - `set_answer` retrieves the latest `answer` from the specified aggregator contract and stores it in the `_answer` internal storage variable. - `read_answer` reads the stored `_answer` value. 1. Navigate to `/scripts/src/consumer/` and open the [`deploy_aggregator_consumer`](https://github.com/smartcontractkit/chainlink-starknet/blob/develop/examples/contracts/aggregator_consumer/scripts/src/consumer/deploy_aggregator_consumer.cairo) script. 2. Update the `aggregator_address` variable within the `main` function with the ETH / USD Chainlink proxy aggregator contract address on Starknet Sepolia : 0x228128e84cdfc51003505dd5733729e57f7d1f7e54da679474e73db4ecaad44. 3. Run the [`deploy_aggregator_consumer`](https://github.com/smartcontractkit/chainlink-starknet/blob/develop/examples/contracts/aggregator_consumer/scripts/src/consumer/deploy_aggregator_consumer.cairo) script to deploy a consumer contract to Starknet Sepolia. Use the following command: ```bash make ac-deploy NETWORK=testnet ``` Expect an output similar to the following: ```bash Declaring and deploying AggregatorConsumer Declaring contract... Class hash = 2231728956022539490111802723283413449895905447951536434260474920103409356221 Deploying contract... Transaction hash = 0x46b39d79cf90ed75fbae5d097d6026cf7ab1184e852d36aed336528fc77220d Waiting for transaction to be accepted (59 retries / 295s left until timeout) AggregatorConsumer deployed at address: 2956260449156927152048242588422796467290585049226993045191257886158889626959 command: script run status: success ``` The consumer address is represented in its decimal form: `2956260449156927152048242588422796467290585049226993045191257886158889626959`. You can use a [converter tool](https://www.rapidtables.com/convert/number/decimal-to-hex.html) to get the hexadecimal format. In this example, the hexadecimal equivalent is `0x06892f22691473dc5a413a7626062604155516ec91a82d3734dc68bcf3d72d4f`. Save your consumer address for the next steps. 4. In `/scripts/src/consumer/`, open the [`set_answer`](https://github.com/smartcontractkit/chainlink-starknet/blob/develop/examples/contracts/aggregator_consumer/scripts/src/consumer/set_answer.cairo) script and update the `consumer_address` variable with your deployed consumer address. 5. Run the following command to update the answer: ```bash make ac-set-answer NETWORK=testnet ``` This command runs the [`set_answer`](https://github.com/smartcontractkit/chainlink-starknet/blob/develop/examples/contracts/aggregator_consumer/scripts/src/consumer/set_answer.cairo) script to retrieve the latest `answer` from the ETH / USD aggregator contract and stores it in the internal storage variable `_answer` of your consumer contract. Expect an output similar to the following: ```bash Result::Ok(CallResult { data: [3374557091160001179079409876363576078229871685244120671563970051729036896526] }) Result::Ok(CallResult { data: [340282366920938463463374607431768224268, 354661371569, 54349, 1711716556, 1711716514] }) Transaction hash = 0x20edd6388041d78a36f153a7694fd8c0849d81aff3fc2d5a0606d7275ea4837 Waiting for transaction to be accepted (59 retries / 295s left until timeout) Result::Ok(InvokeResult { transaction_hash: 930889525374955449815155593419397099236202282693723934594292211164864202807 }) command: script run status: success ``` 6. In `/scripts/src/consumer/`, open the [`read_answer`](https://github.com/smartcontractkit/chainlink-starknet/blob/develop/examples/contracts/aggregator_consumer/scripts/src/consumer/read_answer.cairo) script and update the `consumer_address` variable with your deployed consumer address. 7. Run the following command to read the stored `answer` value from your consumer contract: ```bash make ac-read-answer NETWORK=testnet ``` Expect an output similar to the following: ```bash Result::Ok(CallResult { data: [354661371569] }) command: script run status: success ``` The answer on the ETH / USD feed uses 8 decimal places, so an answer of `354661371569` indicates an ETH / USD price of `3546.61371569`. Each feed uses a different number of decimal places for answers. You can find the correct number of decimal places on the [Price Feed addresses](/data-feeds/price-feeds/addresses?network=starknet) page by clicking the Show more details checkbox. --- # Data Feeds guides (Starknet Foundry) Source: https://docs.chain.link/data-feeds/starknet/tutorials/snfoundry You can explore several comprehensive guides to learn how to use Data Feeds on Starknet using [Starknet Foundry](https://github.com/foundry-rs/starknet-foundry). These tutorials provide step-by-step guidance to help you understand how to locally experiment with or integrate Chainlink Data Feeds into your Starknet projects. ## Guides - [Read Data from Chainlink Data Feeds](/data-feeds/starknet/tutorials/snfoundry/read-data) - [Deploy and interact with a Consumer Contract](/data-feeds/starknet/tutorials/snfoundry/consumer-contract) - [Experiment with Data Feeds using Starknet Devnet RS](/data-feeds/starknet/tutorials/snfoundry/sn-devnet-rs) --- # Read Data from Data Feeds on Starknet using Starknet Foundry Source: https://docs.chain.link/data-feeds/starknet/tutorials/snfoundry/read-data In this example, you will read from a Chainlink price feed on Starknet using the Starknet Foundry toolkit. You will call the `latest_round_data` function on the ETH / USD aggregator proxy contract to read the latest round of data. ## Requirements Make sure you have the [Starknet Foundry](https://github.com/foundry-rs/starknet-foundry) toolkit installed. You can check your current version by running snforge --version or sncast --version in your terminal. Follow the [installation](https://github.com/foundry-rs/starknet-foundry#installation) guide if necessary. ## Tutorial Run the following command to read data from the ETH / USD data feed proxy contract: ```bash sncast \ --url https://starknet-sepolia.public.blastapi.io/rpc/v0_7 \ call \ --contract-address 0x228128e84cdfc51003505dd5733729e57f7d1f7e54da679474e73db4ecaad44 \ --function "latest_round_data" \ ``` Expect an output similar to the following: ```bash command: call response: [0x10000000000000000000000000000320c, 0x5293770eb1, 0xd44d, 0x6606b8cc, 0x6606b8a2] ``` The example output contains an array with the hex-encoded latest round of data for the ETH / USD price feed. The array contains the following values: | Value name | Hex-encoded value | Decoded value | Description | | ------------ | ------------------------------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | `round_id` | `0x10000000000000000000000000000320c` | `340282366920938463463374607431768224268` | The unique identifier of the data round | | `answer` | `0x5293770eb1` | `354661371569` | The actual data provided by the data feed, representing the latest price of an asset in the case of a price feed | | `block_num` | `0xd44d` | `54349` | The block number at which the data was recorded on the blockchain | | `started_at` | `0x6606b8cc` | `1711716556` | The Unix timestamp indicating when the data round started | | `updated_at` | `0x6606b8a2` | `1711716514` | The Unix timestamp indicating when the data was last updated | For a complete list of Chainlink Price Feeds available on Starknet, see the [Price Feed Contract Addresses](/data-feeds/price-feeds/addresses?network=starknet) page. Note: This example uses a [Blast API](https://blastapi.io/public-api/starknet) RPC endpoint. You can interact with the network using any other Starknet Sepolia RPC provider, such as [Alchemy](https://www.alchemy.com/starknet) or [Infura](https://www.infura.io/networks/ethereum/starknet). --- # Experiment with Data Feeds using Starknet Foundry and Starknet Devnet RS Source: https://docs.chain.link/data-feeds/starknet/tutorials/snfoundry/sn-devnet-rs This guide employs [Starknet Devnet RS](https://github.com/0xSpaceShard/starknet-devnet-rs), a local Docker-based testnet environment for Starknet. It provides code examples, [scripts](https://github.com/smartcontractkit/chainlink-starknet/tree/develop/examples/contracts/aggregator_consumer/scripts/src), and [commands](https://github.com/smartcontractkit/chainlink-starknet/blob/develop/examples/contracts/aggregator_consumer/Makefile) that enable you to deploy and experiment with your own aggregator and consumer contracts. You can use a set of prefunded accounts to interact with Chainlink Data Feeds on Starknet without deploying to a live network. ## Requirements ### Set up your environment This guide uses the [Starknet Foundry](https://github.com/foundry-rs/starknet-foundry) toolkit and the [Scarb](https://github.com/software-mansion/scarb) project management tool so you can compile, deploy, and interact with your Starknet smart contracts. - **Starknet Foundry**: Install Starknet Foundry **v0.21.0**. You can check your current version by running snforge --version or sncast --version in your terminal and [install the required version](https://github.com/foundry-rs/starknet-foundry#installation) if necessary. - **Scarb**: Install Scarb **v2.6.4**. You can check your current version by running scarb --version in your terminal and [install the required version](https://docs.swmansion.com/scarb/download.html#install-via-installation-script) if necessary. Alternatively, you can use the [asdf](https://asdf-vm.com/guide/introduction.html) tool version manager to install both Starknet Foundry and Scarb. Read the setup instructions for [Starknet Foundry](https://github.com/foundry-rs/asdf-starknet-foundry) and [Scarb](https://docs.swmansion.com/scarb/download.html#install-via-asdf) for more information. - **Docker**: Install [Docker Desktop](https://docs.docker.com/get-docker/). You will run Starknet Devnet RS in a Docker container. ### Clone and configure the code examples repository 1. Clone the [chainlink-starknet](https://github.com/smartcontractkit/chainlink-starknet/) repository, which includes the example contracts for this guide: ```bash git clone https://github.com/smartcontractkit/chainlink-starknet.git ``` 2. Navigate to the `aggregator_consumer` directory: ```bash cd chainlink-starknet/examples/contracts/aggregator_consumer/ ``` 3. Open your `snfoundry.toml` file and locate the `[sncast.devnet]` section. Make sure the `url` points to the correct endpoint for connecting to the Starknet node running inside the Docker container. By default, it is: ```bash [sncast.default] url = "http://127.0.0.1:5050/rpc" ``` After you prepare the requirements, check to make sure the required tools are configured correctly by running the tests: ```bash make test ``` The contracts should compile successfully and the tests should pass. ## Tutorial > **NOTE: Contract addresses for Starknet Devnet RS** > > If you followed the [onchain](/data-feeds/starknet/tutorials/snfoundry/consumer-contract) guide, [clone the > chainlink-starknet > repository](/data-feeds/starknet/tutorials/snfoundry/sn-devnet-rs#clone-and-configure-the-code-examples-repository) > again to make sure you start from a clean state. By default, some addresses used in this guide are hardcoded to work > with the Starknet Devnet RS environment. ### Run Starknet Devnet RS and add a prefunded account 1. Make sure Docker Desktop is running. 2. Execute the following command to run a Starknet Devnet RS container: ```bash make devnet ``` Note: Executing this command when a container is running stops and recreates the container, which helps restart from a clean state. 3. The Starknet devnet container includes a set of prefunded accounts. To execute the scripts from this guide, add one of these accounts to a local `accounts.json` file by running the following command: ```bash make add-account ``` Expect an output similar to the following: ```bash Importing a prefunded account from starknet devnet container... command: account add Your accounts: { "alpha-goerli": { "devnet-account": { "address": "0x4b3f4ba8c00a02b66142a4b1dd41a4dfab4f92650922a3280977b0f03c75ee1", "class_hash": "0x61dac032f228abef9c6626f995015233097ae253a7f72d68552db02f2971b8f", "deployed": true, "legacy": false, "private_key": "0x57b2f8431c772e647712ae93cc616638", "public_key": "0x374f7fcb50bc2d6b8b7a267f919232e3ac68354ce3eafe88d3df323fc1deb23" } } } ``` ### Deploy and interact with a mock aggregator contract 1. Declare and deploy a mock aggregator contract to the devnet by using the [`deploy_mock_aggregator`](https://github.com/smartcontractkit/chainlink-starknet/blob/develop/examples/contracts/aggregator_consumer/scripts/src/mock_aggregator/deploy_mock_aggregator.cairo) script. Run the following command: ```bash make ma-deploy NETWORK=devnet ``` Expect an output similar to the following: ```bash Declaring and deploying MockAggregator Declaring contract... Transaction hash = 0x1d4cb925322e0f9645cf0bf4028b7fca963b5325cc685784c0f6e22628de8ad Class hash = 3238358970964595466962588940073124312643094641461979698784985925711454737896 Deploying contract... Transaction hash = 0x6b065766a26105fbd9bf55be46675d2c2b4b0dd9a85db5ab0b6a1f8ad04f743 MockAggregator deployed at address: 1708487128334545444076626254389547159540422020354796381590079145465639129383 command: script run status: success ``` 2. Read the latest round of data by using the [`read_latest_round`](https://github.com/smartcontractkit/chainlink-starknet/blob/develop/examples/contracts/aggregator_consumer/scripts/src/aggregator/read_latest_round.cairo) script. Run the following command: ```bash make agg-read-latest-round NETWORK=devnet ``` Expect an output similar to the following: ```bash Result::Ok(CallResult { data: [0, 0, 0, 0, 0] }) command: script run status: success ``` The default data on the mock aggregator contract is all set to zeros. 3. Set the latest round of data to values defined in the [`set_latest_round`](https://github.com/smartcontractkit/chainlink-starknet/blob/develop/examples/contracts/aggregator_consumer/scripts/src/mock_aggregator/set_latest_round.cairo) script. Run the following command: ```bash make ma-set-latest-round NETWORK=devnet ``` Expect an output similar to the following: ```bash Transaction hash = 0x3f01f4595dcf20b4f355720540d5279efc0fff306d4e848dda4550cdd2a5367 Result::Ok(InvokeResult { transaction_hash: 1781197671452105104356220458359085564429259248315865550090042149835146679143 }) command: script run status: success ``` 4. Read the latest round of data again to verify the updated values: ```bash make agg-read-latest-round NETWORK=devnet ``` Expect an output similar to the following: ```bash Result::Ok(CallResult { data: [1, 1, 12345, 1711716556, 1711716514] }) command: script run status: success ``` ### Deploy and interact with a mock aggregator and a mock consumer contract The mock aggregator consumer contract is initialized with the address of an aggregator mock contract during the contract's constructor call and contains two functions: - `set_answer` retrieves the latest `answer` from the specified aggregator contract and stores it in the internal storage variable `_answer`. - `read_answer` reads the stored `_answer` value. 1. Run the following command to (re)start the Starknet Devnet RS container: ```bash make devnet ``` 2. If you haven't already, add a prefunded account to the local `accounts.json` file using the following command: ```bash make add-account ``` 3. Declare and deploy both a mock aggregator and a consumer contracts to the devnet by running the following command: ```bash make devnet-deploy ``` This command executes both [`deploy_mock_aggregator`](https://github.com/smartcontractkit/chainlink-starknet/blob/develop/examples/contracts/aggregator_consumer/scripts/src/mock_aggregator/deploy_mock_aggregator.cairo) and [`deploy_aggregator_consumer`](https://github.com/smartcontractkit/chainlink-starknet/blob/develop/examples/contracts/aggregator_consumer/scripts/src/consumer/deploy_aggregator_consumer.cairo) scripts. Expect an output similar to the following: ```bash Declaring and deploying MockAggregator Declaring contract... Transaction hash = 0x568d29d07128cba750845b57a4bb77a31f628b6f4288861d8b31d12e71e4c3b Deploying contract... Transaction hash = 0xfbc49eb82894a704ce536ab904cdee0fd021b0fba335900f8b9b12cfcd005f MockAggregator deployed at address: 1566652744716179301065270359129119857774335542042051464747302084192731701184 command: script run status: success Declaring and deploying AggregatorConsumer Declaring contract... Transaction hash = 0x3f11f08103e263690b1eeac76c25ce7b003c86d2d1c0492815d1ddb39f58e8e Deploying contract... Transaction hash = 0xfe5b1ed51a435117098343b5d0fdc1c32fd493f1220ff2c69c732f5bb805d8 AggregatorConsumer deployed at address: 2775662320989421053891107164335108610292525354452508848326323790006553228656 command: script run status: success ``` Once the contracts are deployed, experiment with both contracts and the available [scripts](https://github.com/smartcontractkit/chainlink-starknet/tree/develop/examples/contracts/aggregator_consumer/scripts/src) and [commands](https://github.com/smartcontractkit/chainlink-starknet/blob/develop/examples/contracts/aggregator_consumer/Makefile) as needed for your projects. For instance, you can set new values for the latest round of data on the aggregator contract, retrieve and set the `answer` on the consumer contract, and read the `answer` from the consumer contract: ```bash make ma-set-latest-round NETWORK=devnet && make ac-set-answer NETWORK=devnet && make ac-read-answer NETWORK=devnet ``` --- # Smart Value Recapture (SVR) Feeds Source: https://docs.chain.link/data-feeds/svr-feeds > **NOTE: SVR integrated with Titan OFA** > > SVR now supports multiple orderflow auctions in parallel on Ethereum mainnet, including Flashbots and Titan. > Searchers participating in SVR should integrate with all supported orderflow auction providers to maximize their > competitiveness. > > Reach out to [[devrel@smartcontract.com](mailto:devrel@smartcontract.com)](mailto:devrel@smartcontract.com) for assistance getting > onboarded. Searchers can [subscribe here](https://chainlinkcommunity.typeform.com/to/s55Hu2tK) to receive timely > Chainlink SVR updates. > **TIP: SVR Now Available on Base, Arbitrum, and BNB Chain** > > Chainlink SVR has expanded beyond Ethereum Mainnet and is now available on Base, Arbitrum, and BNB Chain via the > Atlas auction system. See the > [Atlas searcher onboarding guide](/data-feeds/svr-feeds/searcher-onboarding-atlas) for integration > details. [Chainlink Smart Value Recapture](https://blog.chain.link/chainlink-smart-value-recapture-svr/) (SVR) Feeds introduce a novel and secure way to recapture Oracle Extractable Value (OEV)—a subset of non-toxic Maximal Extractable Value (MEV) associated with oracle updates that is most commonly observed during the liquidation process of lending protocols. The value recaptured by SVR provides DeFi protocols with an additional revenue stream while also supporting the economic sustainability of Chainlink oracle. SVR Feeds are read in the same manner as standard Chainlink Data Feeds (see [Using Data Feeds](/data-feeds/using-data-feeds)), and simply require that users specify an SVR-enabled feed address. > **NOTE: Talk to an expert** > > [Contact us](https://chain.link/contact?ref_id=pricefeeds) to talk to an expert about integrating Chainlink SVR with > your applications. > **CAUTION: Notice** > > Developers remain responsible for ensuring that protocol risk parameters are configured appropriately and that the > operation and performance of Chainlink SVR matches expectations. Chainlink SVR operates on the same underlying > architecture as Chainlink Data Feeds. Please review the Chainlink Data Feeds [Developer > Responsibilities](/data-feeds/developer-responsibilities) and Chainlink [Terms of Service](https://chain.link/terms) > for important information and disclosures. By using Chainlink SVR, you acknowledge and agree to these terms. ## Understanding MEV and OEV ### Maximal Extractable Value (MEV) Maximal Extractable Value (MEV) refers to the value derived from the ability of block proposers (e.g., validators) to include, exclude, or change the order of transactions in the blocks they produce. Specialized **searchers** typically identify MEV opportunities, such as liquidation or arbitrage, and bid for optimal inclusion in the block via a competitive auction. The value is then captured by the participants of the block building process such as searchers, builders, and validators. ### Oracle Extractable Value (OEV) Oracle Extractable Value (OEV) refers to a subset of MEV created during the transmission of **oracle reports** onchain and their subsequent consumption by onchain applications. For example, when a price oracle report reveals that a collateralized position is at-risk and eligible for liquidation, searchers compete to place a **liquidation transaction** immediately after the oracle update transaction (a process known as **backrunning**). Because oracles supply these price updates, the resulting liquidation MEV is inherently "oracle-related". ## Chainlink Smart Value Recapture (SVR) Chainlink Smart Value Recapture (SVR) extends standard Chainlink Price Feeds with an **optional private transmission flow**. By sending oracle updates through a **dual aggregator** architecture, SVR enables an **auction** for the opportunity to backrun liquidations. This approach enables **non-toxic MEV** recapture, where the **DeFi protocol** and the **Chainlink Network** share in the payment offered by the searcher for the right to backrun the liquidation instead of letting it leak entirely to third parties. ### Why Use SVR? - **Targeted at Liquidations (Non-Toxic MEV Capture)**\ SVR is purpose-built for recapturing non-toxic **liquidation-related OEV** via backrunning and cannot be used for harmful forms of MEV such as frontrunning or sandwich attacks. - **Minimal Integration Changes**\ Chainlink SVR Feeds maintain the **same aggregator interface** and data structure as standard Chainlink Price Feeds. Most protocols can simply point to the new SVR contracts with **minimal** changes required. - **Economic Sustainability**\ By recapturing OEV that would have otherwise been leaked to third parties, SVR helps support the economic sustainability of the DeFi ecosystem. Recaptured OEV revenue is split between integrating DeFi protocols and the Chainlink Network at a standard rate. - **Fallback Security**\ Chainlink SVR uses **standard Price Feeds** as a fallback, ensuring pricing data is continually made available to protocols via the public mempool in situations where the private route (e.g., Flashbots) fails. - **Proven Oracle Infrastructure**\ Chainlink SVR is built on the **Chainlink Decentralized Oracle Network (DON)** infrastructure that has already helped secure tens of billions in DeFi TVL and underpins existing Chainlink Data Feeds. ### How SVR Works ![Image](/images/data-feed/svr/svr-integration-flow.webp) 1. **Dual Transmission** - **(1a) Standard Feed (Public Route)**: The Chainlink Data DON (triggered by a heartbeat or deviation threshold) sends a price report to the **Standard Aggregator** via the public mempool—this is the traditional Chainlink flow. If anything goes wrong with the private route, the system can still rely on the standard aggregator's updates. - **(1b) SVR Feed (Private Route)**: Simultaneously, the Data DON sends the **same** price report to the **SVR Aggregator** through a private channel (e.g., Flashbots MEV-Share). This private route creates an **auction** for liquidations. 2. **MEV-Share Auction** - **(2) Liquidation Bid**: In the private channel, searchers see the incoming oracle report and bid to backrun the price update with a liquidation transaction. Builders select the highest bid, bundling that liquidation in the same block. If no bid is placed, the price update can still be published onchain without any bundled liquidation. If not, the fallback in the dual aggregator is triggered. 3. **SVR Update and Liquidation** - **(3a) SVR Price Report**: The winning bundle is published onchain, and the **SVR Feed** is updated with the new price. - **(3b) Liquidation Execution**: In the same block, the **backrunning** transaction can use the freshly updated price to liquidate the undercollateralized position. The oracle-related MEV can thereby be partly recaptured by the DeFi protocol and the Chainlink Network. 4. **Token Price Consumption** - **(4) DeFi Protocol**: The updated price from the **SVR Feed** is now available to the DeFi protocol (e.g., Aave) for accurate liquidation logic or other price-dependent actions. **Fail-Safe Fallback Mechanism**\ If the **private route** fails or times out, the SVR feed automatically **reverts** to the **Standard Feed price** after a configurable delay. This delay can be set to any amount of seconds. This helps ensures the feed doesn't stall and that price data is accessible through the public route if the private channel is unavailable. ## SVR Feed Variants Chainlink SVR Feeds are available in three variants: ### SVR (Shared) Canonical shared SVR feeds for use by any protocol. These are the recommended feeds for new integrations. ### Aave SVR Feeds Dedicated SVR feeds exclusively for the Aave protocol. ### SVR-Backup (Legacy) Legacy shared SVR feeds that are still actively used by existing applications. Searchers should continue to monitor these feeds. New integrations should use the [SVR (Shared)](#svr-shared) feeds instead. ## Default SVR Activation Chainlink SVR is activated by default for certain Chainlink Data Feeds on certain blockchain networks, currently Base and Ink. When SVR is enabled by default, new and existing lending market deployments recapture OEV without any additional integration work. Integrators should expect zero service disruption during activation, and no action is needed on their part. On these networks, SVR feeds have an **SVR** label on the [Feed Addresses](/data-feeds/price-feeds/addresses?network=base\&showSvr=true) page and on [data.chain.link](https://data.chain.link/). Previously deployed SVR feeds remain available and function as expected, and can be identified with the **SVR-Backup** label. For any questions, contact [chainlink_data_feeds@smartcontract.com](mailto:chainlink_data_feeds@smartcontract.com). Protocols can also opt out at any time by reading from the "Standard" proxy that complements every SVR proxy for the relevant feed. ## How Protocols Can Utilize SVR Feeds ### 1. Identify the SVR Feed Address To get started, please fill out [this form](https://chainlinkcommunity.typeform.com/svr-signup) to ensure protocol compatibility and proper recapture of OEV revenue. In many situations, you only need to point your protocol to an SVR version of the Chainlink Price Feed. To find the correct address for the asset pair you wish to secure with SVR, visit the [Feed Addresses](/data-feeds/price-feeds/addresses) page and filter the feeds to display only SVR feeds. ### 2. Read from the SVR Feed In your smart contract, import and reference the **AggregatorV3Interface** just as you normally would, but **use the SVR aggregator's address**. ```solidity import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; contract SVRConsumer { AggregatorV3Interface internal svrFeed; constructor(address _svrFeedAddress) { svrFeed = AggregatorV3Interface(_svrFeedAddress); } function getLatestPrice() public view returns (int256) { ( , /* uint80 roundID */ int256 price /* uint256 startedAt */ /* uint256 timeStamp */ /* uint80 answeredInRound */, , , ) = svrFeed.latestRoundData(); return price; } } ``` This code sample is essentially the same as reading from a standard Chainlink Price Feed. For more details, see [Using Data Feeds](/data-feeds/using-data-feeds). ## Searcher Guides Chainlink SVR supports two auction paths depending on the network: | Network | Auction System | Guide | | -------------------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------- | | Ethereum Mainnet | Flashbots MEV-Share | [Searcher Onboarding: Ethereum Mainnet](/data-feeds/svr-feeds/searcher-onboarding-ethereum) | | Base, Arbitrum, BNB Chain, Monad | Atlas | [Searcher Onboarding: Atlas (Base, Arbitrum, BNB Chain, Monad)](/data-feeds/svr-feeds/searcher-onboarding-atlas) | ## Economics and Revenue Split When a protocol integrates Chainlink SVR Feeds, the recaptured oracle-related MEV is split between the integrating DeFi protocol and the Chainlink Network. This split provides DeFi protocols with an additional revenue stream while also supporting the economic sustainability of Chainlink oracles. Note that the split may be subject to change, with the goal of generating sustainable economics between DeFi protocols and the oracles that power them. ## Risk and Disclaimers - **Delay Risks**: SVR introduces a small, configurable delay to allow for the MEV-Share auction. In the unlikely event of a private-route failure, the dual aggregator reverts to the standard feed price update after a delay. To learn more about delay risks, and how you can parameterize your protocol to account for them, refer to [this research blog](https://blog.chain.link/chainlink-svr-analysis/). - **Liquidation Competition**: Multiple liquidations triggered by the same price update compete for blockspace. SVR is agnostic to which liquidator wins. SVR maximizes for the highest payment, and thus the highest recapture rate, on the auction. - **MEV Does Not Disappear**: SVR recaptures only the oracle-related portion of MEV. Protocol builders should remain MEV-aware when designing their applications. - **Dynamic Recapture Rates**: SVR aims to recapture as high a portion of oracle-update-related MEV as possible. Actual results depend on market conditions, competition among searchers, and factors such as protocol design. > **CAUTION: Notice** > > Chainlink SVR Feeds recapture oracle-initiated liquidation-related MEV (OEV), but they do not eliminate other forms > of MEV such as sandwich or frontrunning attacks. Developers remain responsible for ensuring that protocol risk > parameters are configured appropriately and that the operation and performance of Chainlink SVR matches > expectations. --- # SVR Searcher Onboarding: Atlas Source: https://docs.chain.link/data-feeds/svr-feeds/searcher-onboarding-atlas > **NOTE: Searcher Onboarding** > > SVR on Atlas (every chain outside of ETH mainnet) uses a reputation system with **two groups**. Unless otherwise specified for a chain, Atlas includes up to **2 searcher bids** per transaction for on-chain settlement: > > - One slot is always reserved for the high-reputation set > - The second slot is open to both high- and low-reputation searchers > > Final winner ordering is determined by bid competitiveness, not reputation. If a low-reputation searcher submits the highest valid bid, that searcher can win. > > Onboarding and access to the high-reputation set is permissionless and does not require communication with Chainlink Labs. For any questions or comments about SVR, email [devrel@smartcontract.com](mailto:devrel@smartcontract.com) > > Searchers can [subscribe here](https://chainlinkcommunity.typeform.com/to/s55Hu2tK) to receive timely Chainlink SVR updates. [Chainlink Smart Value Recapture (SVR)](/data-feeds/svr-feeds) has expanded to Base, Arbitrum, BNB Chain, and Monad with a new auction system, **Atlas**. This guide provides existing Chainlink SVR searchers with the technical specifications and integration requirements for participating in all non-ETH mainnet SVR auctions. ## Core Components - **SVR Price Feeds**: Modified Chainlink aggregators used for SVR auction transactions. - **Atlas**: A set of smart contracts and off-chain auction infrastructure. - **Solver Network**: Network of liquidators. ## Terminology The terms **Searcher** and **Solver** are used interchangeably throughout this guide. ## Operational Recommendations Recommended native token amounts for bonding: - **Base and Arbitrum**: 0.1 ETH - **BNB Chain**: 1 BNB ## Supported Protocols - AAVE - Compound - Venus ## Contract References The following contracts are deployed at version **v1.6.4**. ### Base Contract Address Atlas (v1.6.4) DappControl ### Arbitrum Contract Address Atlas (v1.6.4) DappControl ### BNB Chain Contract Address Atlas (v1.6.4) DappControl ### Monad Contract Address Atlas (v1.6.4) DappControl ## Feed Aggregator Addresses > **NOTE** > > Searchers need to listen to the **Aggregator address** for price reports, not the proxy address. You can find the > aggregator address by reading `aggregator` in the proxy contract. For convenience, we've included the aggregator > address in the table below. Use **Aave-SVR** type feeds for Aave, **SVR** type feeds for other protocols, and > **SVR-Backup** for the legacy shared feeds. For the full list of SVR feeds, see the [SVR Feeds > page](/data-feeds/svr-feeds). ## Guide ### High-Level Atlas is conceptually similar to MEV-Share, built to work on any EVM chain without requiring block builders. Instead of relying on block builders to bundle together the oracle update and the liquidation transaction, the oracle update and the liquidation are both represented as [EIP-712](https://eips.ethereum.org/EIPS/eip-712) messages and are bundled together into a single atomic EVM transaction similar to multi-call or [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337). > **CAUTION: Important** > > The final bundled transaction is **not submitted by the searcher**. It is submitted by the Chainlink oracle > network. > > Searchers must have **bonded native tokens** with the Atlas smart contracts in order to reimburse the Chainlink > oracle for the gas costs of both the **searcher operation** and the **oracle update**. > > Gas prices for these transactions are chosen by the Chainlink oracle. The selected gas price is provided to the > searcher at the start of the auction. > > Searchers must sign for a `gasPrice` that is **exactly equal** to the gas price specified by the > Chainlink oracle. > > Searchers must not attempt to use a gas limit for their operation that is greater than the allowed > `SolverGasLimit`. Query this value by calling `getSolverGasLimit` on the > `DappControl` > contract. Sending `SolverOps` with a higher gas limit will not work. > > Since auction and `auctionSolution` payloads can exceed default read/write buffer sizes in popular > WebSocket libraries, explicitly configure read and write buffers to greater than **10 KB**. > > Solvers should expect that their long-lived connections to the SVR endpoint will be disconnected, and should > implement **auto-reconnect** functionality. ### Endpoints | Endpoint | URL | | ------------------ | --------------------------------------------- | | Bid endpoint | `https://svr-bid-endpoint.chain.link/` | | Searcher WebSocket | `wss://svr-bid-endpoint.chain.link/ws/solver` | ### Simulation and Execution SolverOps that fail simulation will not show up on-chain. **Example transaction:** [BscScan 0x6194065e](https://bscscan.com/tx/0x6194065e247c98cd5b665582227cf70c00cc7a2016ff1448f1286ad592cf7c05) ### Signing the Payload The payload that needs to be signed is the Ethereum message ([EIP-191](https://eips.ethereum.org/EIPS/eip-191)) in the following format: ``` :: ``` Note the colon (`:`) between each field. Most libraries provide built-in helpers for signing messages: - **ethers.js**: [Signer.signMessage](https://docs.ethers.org/v5/api/signer/#Signer-signMessage) - **geth**: [accounts.go#L184](https://github.com/ethereum/go-ethereum/blob/master/accounts/accounts.go#L184) **Example signed payload:** ``` 89599d40-decb-4f1c-97bb-c3e101d790af:0x6044c22ab257659b74b1eb4cf2f8f65e0bcc2d9fe832279efb42a6700873fa74:0x5003676390dfe662Af408Eb0bf13e182aDcaCE0a ``` ### WebSocket Subscription Connect to the searcher WebSocket endpoint and subscribe to user operations: **Subscription payload:** ```json { "jsonrpc": "2.0", "id": 1, "method": "solver_subscribe", "params": ["userOperations"] } ``` **Example response:** ```json { "jsonrpc": "2.0", "id": 1, "result": "5ed17bf8-ed27-4625-97ef-447554594a3c" } ``` **Example notification:** ```json { "jsonrpc": "2.0", "method": "solver_subscription", "params": { "subscription": "5ed17bf8-ed27-4625-97ef-447554594a3c", "result": { "auction_id": "1bc9a4ce-4fcf-4eb1-8632-959e2273953e", "partial_user_operation": { "chainId": "0x2105", "userOpHash": "0x21d03d618fa4fb9166ae91f199b774b64390b8bf6ecdd2ae4637096e7b60e5e3", "to": "0xb15BdDC2180cF83B3ECb1eDE074a177c9C7Acc5f", "gas": "0x4e20", "maxFeePerGas": "0x4c4b40", "deadline": "0x19d8c75", "dapp": "0x43b4aae0f98fc9ebd86a1e9496cdb9d7208ee55b", "control": "0x43b4aae0f98fc9ebd86a1e9496cdb9d7208ee55b", "value": "0x0", "data": "0x1ad6fbc3", "from": "0xfc8b8974fc3adb8281a6c4c38d7cc895769a8568" } } } } ``` ### Submitting a Solution ```json { "jsonrpc": "2.0", "id": 1, "method": "solver_submitSolverOperation", "params": [ { "auction_id": "1bc9a4ce-4fcf-4eb1-8632-959e2273953e", "auction_solution": { "from": "0x377136613944bdd5d9f0db22987b7432e76c354f", "to": "0xb15BdDC2180cF83B3ECb1eDE074a177c9C7Acc5f", "value": "0x0", "gas": "0x7a120", "maxFeePerGas": "0xb71b00", "deadline": "0x12aad5bd", "solver": "0x7ff1b456058af5e8f36c4e5c29049f40c0aa945c", "control": "0x43b4aae0f98fc9ebd86a1e9496cdb9d7208ee55b", "userOpHash": "0xbf8c8c00c288558cc0204e8e802a2045655804441c576fec386b888e8619052a", "bidToken": "0xaf88d065e77c8cc2239327c5edb3a432268e5831", "bidAmount": "0x3b9aca00", "data": "0x1a2b3c4d", "signature": "0x786a3a67058a02307066747ebaf09a5704b6d3fd4f891d9390e437530b68b7f91bcd4c12e09035917ca22b40069663c644b1d9eb74b4808028e086484fb1b27b1c" } } ] } ``` **Example response:** ```json { "jsonrpc": "2.0", "id": 1, "result": "null" } ``` ## Bond Bridged ETH to the Auction Contract ### About Bonding While Chainlink oracles submit transactions on-chain and advance the associated gas costs, all searchers are required to atomically pay the gas costs of their liquidation and the oracle update within the transaction. Searchers do not need to explicitly send a payment. Instead, gas costs are automatically deducted from the searcher's bonded balance. ### Gas Repayment Rules The amount of gas repaid by a searcher depends on the outcome of the searcher operation: | Outcome | Gas Charged | | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | | Searcher operation executed successfully | Gas consumed by both the searcher operation and user operation is charged to the searcher. | | Searcher operation executed but failed (searcher responsibility) | Gas consumed by the searcher operation is charged to the searcher. | | Searcher operation executed but failed (bundler responsibility) | No gas charge applied to the searcher. | | Searcher operation not executed | No gas charge applied to the searcher. | ### Bonding and Unbonding Steps #### 1. Select an Account The account (EOA) bonding ETH must be the same account that will sign the searcher operations (defined in the `from` field of the operation). > **NOTE** > > A searcher is limited to one auction per block. If a searcher needs to participate in more than one auction in a > block, they will need as many accounts (EOA) with bonded ETH as needed. #### 2. Deposit and Bond Call the `depositAndBond` function on the Atlas contract: ```solidity interface IAtlas { function depositAndBond(uint256 amountToBond) external payable; } address atlasAddress = address(0x8e098Dfd60aEC9bCf07fd3cA5933e9F22b1b4A0d); uint256 amountToBond = 1e18; IAtlas(atlasAddress).depositAndBond{ value: amountToBond }(amountToBond); ``` > **CAUTION** > > **The bonded amount is credited to the function caller.** Ensure you are calling with the proper account. #### 3. Unbonding Searchers can recover their funds in two steps: 1. Call `unbond` and wait for the unbonding period 2. Call `redeem` ```solidity interface IAtlas { function unbond(uint256 amount) external; function redeem(uint256 amount) external; function ESCROW_DURATION() external view returns (uint256); } address atlasAddress = address(0x8e098Dfd60aEC9bCf07fd3cA5933e9F22b1b4A0d); uint256 amountToUnbond = 1e18; IAtlas(atlasAddress).unbond(amountToUnbond); uint256 escrowDuration = IAtlas(atlasAddress).ESCROW_DURATION(); // Must wait `escrowDuration` blocks before calling `redeem`. IAtlas(atlasAddress).redeem(amountToUnbond); ``` ## How to Deploy a Searcher Contract Searcher execution logic must be held in a smart contract that will be called by the system during transaction execution. The searcher must pay their bid before the end of their execution. ### 1. Requirements The searcher's contract **must** define the following callback function: ```solidity function atlasSolverCall( address solverOpFrom, address executionEnvironment, address bidToken, uint256 bidAmount, bytes calldata solverOpData, bytes calldata forwardedData ) external payable ``` This function is called by Atlas during transaction execution. Parameter breakdown: | Parameter | Description | | ---------------------- | -------------------------------------------------------------------------------------------------- | | `solverOpFrom` | The `from` field of the searcher/solver operation being executed (for safety checks). | | `executionEnvironment` | A unique contract generated for the user/dAppControl pair. The bid must be paid to this address. | | `bidToken` | The bid token. `address(0)` refers to ETH. | | `bidAmount` | The bid amount to be paid to the execution environment. | | `solverOpData` | The data passed by the searcher/solver in the searcher operation's `data` field. | | `forwardedData` | The data returned by previous execution steps (pre-ops and user operation) if enabled by the dApp. | Before the end of `atlasSolverCall` execution, the solver **must**: - Pay `bidAmount` to the `executionEnvironment` address in `bidToken` currency, or face revert. - Pay its gas consumption by calling the Atlas `reconcile` function. - Ensure the caller is the Atlas contract. ### 2. Easy Integration Solvers can inherit their contract from the official `SolverBase` contract. This contract defines the `atlasSolverCall` function, handles safety checks, bid payments, and gas liability payments, so the solver can focus on its custom execution logic only. > **NOTE** > > Atlas must be imported in your project before inheriting from `SolverBase`. The Atlas source code is > available at the [Atlas GitHub repository](https://github.com/FastLane-Labs/atlas). Inheriting from `SolverBase`: ```solidity pragma solidity ^0.8.22; import {SolverBase} from "@atlas/solver/SolverBase.sol"; contract DemoSolver is SolverBase { /* * @notice Constructor * @param weth_ The address of the WETH token * @param atlas_ The address of Atlas */ constructor(address weth_, address atlas_) SolverBase(weth_, atlas_, msg.sender) {} function myMevFunction(address myParam1, uint256 myParam2) external { // Solver MEV logic goes here // At the end of execution, profit should be held in this same contract // The `payBids` modifier in `SolverBase` will take care of paying what is owed } } ``` Once deployed, the contract address must be referenced in the searcher operation's `solver` field. The `data` field must be the encoded `myMevFunction` call: ```solidity address myParam1 = address(0x01); uint256 myParam2 = 999; bytes calldata solverOperationData = abi.encodeCall(DemoSolver.myMevFunction, (myParam1, myParam2)); ``` ## How to Participate in Auctions After bonding atlETH and deploying their smart contract, solvers are finally able to participate in auctions by communicating with the searcher gateway. This guide uses Go and the [Atlas Go SDK](https://github.com/FastLane-Labs/atlas-sdk-go). ### Parallel Auctions On certain assets—primarily BTC and ETH feeds on BNB Chain, Base, and Arbitrum—two separate auctions are created in parallel by different Chainlink nodes for each liquidation opportunity. **These auctions are independent**: there is no matching or coordination between them. **How the parallel auctions behave** Both auctions represent the exact same liquidation opportunity, but: - They are sent to the auction independently. - They can arrive at different times due to latency differences. - They may have slightly different deadlines, based on when each node receives the report. - It is effectively random which auction transaction lands first or succeeds onchain. **Implication for bidding** If you only bid on one auction, you have roughly a 50% chance of winning even if your bid is the highest. To guarantee coverage, you must bid on both auctions using the same strategy. > **CAUTION: Required: Bid on Both Parallel Auctions** > > Always submit bids to **both** auctions when two are created for the same liquidation opportunity. > > Avoid logic that limits bidding to a single auction per price update. > Latency and timing differences between parallel auctions are expected and unavoidable. Duplicate auctions were introduced to improve landing guarantees by increasing the probability that at least one auction succeeds onchain. ### 1. Communicate with the Searcher Gateway Searchers communicate with the system through the Searcher Gateway, which serves as the entry point for reading user operations and submitting solutions. The gateway exposes a compliant JSON RPC API, so we can use the geth RPC client. It is fully compatible with the Atlas API, so all existing Atlas integrations work seamlessly through the gateway. The following code connects to the searcher gateway and defines an event loop where user operation notifications will be received. It validates the notification and builds a solver operation by calling the `isOfInterest` and `buildSolution` functions, which are defined in the next sections. It then sends the solution back to the searcher gateway. ```go // connect.go package searcher_gateway import ( "context" "time" "github.com/FastLane-Labs/atlas-sdk-go/types" "github.com/ethereum/go-ethereum/rpc" "github.com/gorilla/websocket" ) const ( solverNamespace = "solver" userOperationsSubscriptionTopic = "userOperations" submitSolverOperationMethod = "solver_submitSolverOperation" searcherGatewayUrl = "wss://svr-bid-endpoint.chain.link/ws/solver" ) // Notifications are received in this format type UserOperationNotification struct { AuctionId string `json:"auction_id"` PartialUserOperation *types.UserOperationPartialRaw `json:"partial_user_operation"` } func runSearcherGateway() { // Set bigger buffer sizes for the websocket connection dialerOption := rpc.WithWebsocketDialer(websocket.Dialer{ ReadBufferSize: 1024 * 1024, WriteBufferSize: 1024 * 1024, }) // Dial the searcher gateway searcherGatewayClient, err := rpc.DialOptions(context.TODO(), searcherGatewayUrl, dialerOption) if err != nil { panic(err) } // Create a channel to receive notifications var ( uoChan chan *UserOperationNotification sub *rpc.ClientSubscription ) // Subscribe/Resubscribe to user operations notifications subscribe := func() { for { uoChan = make(chan *UserOperationNotification, 32) sub, err = searcherGatewayClient.Subscribe(context.TODO(), solverNamespace, uoChan, userOperationsSubscriptionTopic) if err != nil { // Failed to subscribe, wait a bit and retry time.Sleep(1 * time.Second) continue } break } } subscribe() // Main loop for { select { case <-sub.Err(): // If the subscription errors, resubscribe subscribe() case n := <-uoChan: // Received a notification, check if it's of interest if !isOfInterest(n) { continue } // Build a solution and submit it solution, err := buildSolution(n) if err != nil { panic(err) } // No error means the submission was successful err = searcherGatewayClient.CallContext(context.TODO(), nil, submitSolverOperationMethod, solution) if err != nil { panic(err) } } } } ``` ### 2. Build Solver Operations Here is an example on how to construct a solver operation. ```go // solution.go package searcher_gateway import ( "math/big" "github.com/FastLane-Labs/atlas-sdk-go/config" "github.com/FastLane-Labs/atlas-sdk-go/types" "github.com/FastLane-Labs/atlas-sdk-go/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) // Solutions are sent in this format type Solution struct { AuctionId string `json:"auction_id"` AuctionSolution *types.SolverOperationRaw `json:"auction_solution"` } func buildSolution(n *UserOperationNotification) (*Solution, error) { // Get the solver private key solverPk, err := crypto.HexToECDSA("0xmySolverPrivateKey") if err != nil { return nil, err } var ( // This is the address of the solver account // This account must have bonded atlETH on the Atlas contract solverAccount = crypto.PubkeyToAddress(solverPk.PublicKey) // This is the address of the solver contract // This contract must have the `atlasSolverCall` defined solverContractAddress = common.HexToAddress("0xmySolverContractAddress") // This contains the data to be passed to the `atlasSolverCall` // This should contain the solver execution logic solverData = common.FromHex("0xmySolverData") ) // Build the solver operation solverOperation := &types.SolverOperation{ // The solver account (and signer of this operation) From: solverAccount, // The Atlas address, here we're retrieving it from the user operation To: n.PartialUserOperation.To, // The gas limit for the solver operation Gas: big.NewInt(500_000), // The max fee per gas for the solver operation // It must be equal to or higher than the user operation's max fee per gas // Here we match the user operation's value MaxFeePerGas: new(big.Int).Set(n.PartialUserOperation.MaxFeePerGas.ToInt()), // The deadline for the solver operation (block number) // Here we match the user operation's value Deadline: new(big.Int).Set(n.PartialUserOperation.Deadline.ToInt()), // The solver contract address Solver: solverContractAddress, // The `dAppControl` (module) address // Here we're retrieving it from the user operation Control: n.PartialUserOperation.Control, // The user operation hash UserOpHash: n.PartialUserOperation.UserOpHash, // The bid token, address(0) usually stands for ETH BidToken: common.Address{}, // The bid amount BidAmount: big.NewInt(200_000), // The data to be passed to the solver contract Data: solverData, } // To sign an operation with the SDK, we need to specify the chain ID and the Atlas version // Getting the chain ID from the notification chainId := n.PartialUserOperation.ChainId.ToInt().Uint64() // Getting the Atlas version from the Atlas address specified in the user operation atlasVersion, err := config.GetVersionFromAtlasAddress(chainId, n.PartialUserOperation.To) if err != nil { return nil, err } // Getting the hash of the solver operation (this is the payload that will be signed) hash, err := solverOperation.Hash(chainId, &atlasVersion) if err != nil { return nil, err } // Signing the hash with the solver private key signature, err := utils.SignMessage(hash.Bytes(), solverPk) if err != nil { return nil, err } // Setting the signature to the solver operation solverOperation.Signature = signature // Returning the solution return &Solution{ // The auction ID, as communicated in the notification AuctionId: n.AuctionId, // The solver operation, serialized AuctionSolution: solverOperation.EncodeToRaw(), }, nil } ``` ### 3. Filter User Operations In the above example, we do not filter user operations. The searcher gateway will broadcast every user operation it gets. It's necessary to discard operations that the solver can't bid on. ```go // filter.go package searcher_gateway import ( "math/big" "github.com/ethereum/go-ethereum/common" ) var ( // Filtering notifications for this chain ID only chainId = big.NewInt(8453) // Filtering notifications for the Chainlink dApp control (module) address only dAppControlAddress = common.HexToAddress("0xe15BBa987C002ecc3586e81244517877D294d291") ) // This function returns true if the notification is of interest and false // if it should be discarded func isOfInterest(n *UserOperationNotification) bool { if n.PartialUserOperation.ChainId.ToInt().Cmp(chainId) != 0 { return false } if n.PartialUserOperation.Control != dAppControlAddress { return false } return true } ``` ### 4. Decode User Operations When a notification is of interest, and before building a solver operation, decode the received user operation to ensure you are able to bid on it. To find out what a user operation is intending to do, inspect its `dapp` and `hints` fields: - **`userOperation.dapp`**: The contract address that the user will call. - **`userOperation.hints`**: Three key pieces of information that clarify which feed is being updated and at what price: - `aggregator`: The contract address of the feed. - `medianPrice`: The median reported price, the final updated feed price. - `rawReport`: The full report sent to the aggregator. This is included for visibility, but isn't strictly needed since we already have the `medianPrice`. #### How to Filter Out Chainlink Partnership / Price Updates Once you've identified a potential SVR feed update transmission with the `isOfInterest()` method, it's important to know how to extract: - The **feed address**: This is required to determine which SVR data feed is being updated. - The **new price data**: This is essential for determining if profitable liquidation opportunities exist. The decoding process relies on recognizing that the transaction ultimately calls the `transmitSecondary` function, which updates the price using the report data: ```solidity transmitSecondary( bytes32[3] calldata reportContext, bytes calldata report, bytes32[] calldata rs, bytes32[] calldata ss, bytes32 rawVs ) ``` The `report` parameter has the following structure: ```solidity struct Report { uint32 observationsTimestamp; bytes32 observers; int192[] observations; int192 juelsPerFeeCoin; } ``` The median price, which is used to update the value, could be extracted from the report structure (`rawReport`) by selecting the median observation. If the count is odd, use the central element; if the count is even, take the element at index `length / 2` rather than averaging the two central values. This isn't necessary because the median price has already been pre-calculated and is available as `medianPrice`. The following code demonstrates how to retrieve the feed address and decode the feed price: ```go func GetFeedAddressAndPrice(n *UserOperationNotification) (common.Address, *big.Int, error) { feedAddress := common.HexToAddress(n.PartialUserOperation.Hints["aggregator"].(string)) decodedPrice, err := hex.DecodeString(strings.TrimPrefix(strings.ToLower(n.PartialUserOperation.Hints["medianPrice"].(string)), "0x")) if err != nil { return common.Address{}, nil, fmt.Errorf("failed to decode medianPrice hint: %w", err) } feedPrice := new(big.Int).SetBytes(decodedPrice) return feedAddress, feedPrice, nil } ``` ## Tracing Solver Operation Results FastLane provides a query API for solvers to trace what happened to their solver operation. Results are cached for 1 hour. Currently with an auction time of 2s, you should wait \~3 seconds before querying this API for a given SolverOp. **URL:** `https://solver-query-api-fra.fastlane-labs.xyz/` > **CAUTION: Use EIP-55 checksummed addresses** > > All addresses passed to this API (e.g. `solverOperationFrom`) **must be in > [EIP-55](https://eips.ethereum.org/EIPS/eip-55) checksum format**. Lowercased or otherwise non-checksummed addresses > will not match the cached entry and the API will return `"solver operation not found"`, even if your solver operation > was actually submitted successfully. **Example request:** ```json { "jsonrpc": "2.0", "id": "sample-id", "method": "solver_getSolverOperationResult", "params": [ { "auctionId": "abcd-1234", "userOperationHash": "0x1234...", "solverOperationFrom": "0xabcd...", "signature": "0xabcd..." } ] } ``` **Example response:** ```json { "jsonrpc": "2.0", "id": "sample-id", "result": { "auctionId": "abcd-1234", "solverOperationFrom": "0xabcd...", "result": "included" } } ``` ## Common Mistakes **Not paying the bid to the ExecutionEnvironment contract** This won't happen if you inherit from `SolverBase`. **Solver operation arrives too late** If the solver operation arrives after the auction duration has elapsed, it will not be included. The query API will communicate the error `"solver operation not found"`. **Querying the API without an EIP-55 checksummed address** If you query the [solver operation results API](#tracing-solver-operation-results) using an address (e.g. `solverOperationFrom`) that is not in [EIP-55](https://eips.ethereum.org/EIPS/eip-55) checksum format, the lookup will fail and the query API will return `"solver operation not found"` — even if your solver operation was actually received and processed. Always pass checksummed addresses when calling the query API. **Not enough bonded atlEth** Read the [bonding guide](#bond-bridged-eth-to-the-auction-contract) to learn how to bond atlETH. This error will be communicated in the query API response. **Solver operation signer is different from the owner of the Solver Contract** [`SolverBase`](https://github.com/FastLane-Labs/atlas/blob/main/src/contracts/solver/SolverBase.sol) has an owner, and the solver operation will revert if the signer differs from the owner. The query API will communicate error `SolverOpReverted`. **Incorrect solver signature** The solver signature is an [EIP-712](https://eips.ethereum.org/EIPS/eip-712) signature. The following domain should be used: ```javascript { name: "AtlasVerification", version: "", // Version of the Atlas Verification contract chainId: 1, // chain id verifyingContract: "", // Atlas Verification contract address } ``` An incorrect signature will be communicated by the query API. **Incorrect Bid Token** The bid token is described in the dapp control contract (`userOperation.control`). If set to `address(0)`, the bid token is the chain's native token. Paying with the wrong token will result in a `SolverOpReverted` error. **A reverting solver operation** If the solver operation reverts for any reason, the query API will communicate error `SolverOpReverted`. --- # SVR Searcher Onboarding: Ethereum Mainnet Source: https://docs.chain.link/data-feeds/svr-feeds/searcher-onboarding-ethereum > **NOTE: Searcher Onboarding** > > To onboard as a searcher to SVR, reach out to [devrel@smartcontract.com](mailto:devrel@smartcontract.com). Searchers > can [subscribe here](https://chainlinkcommunity.typeform.com/to/s55Hu2tK) to receive timely Chainlink SVR updates. This guide explains how to identify and bid on **Chainlink SVR** liquidation opportunities on **Ethereum Mainnet** through **Flashbots MEV-Share**. SVR Feeds support a private transmission channel for oracle price updates, enabling searchers to backrun new prices with liquidation transactions and compete for profit. For Base, Arbitrum, and BNB Chain, see the [Atlas searcher onboarding guide](/data-feeds/svr-feeds/searcher-onboarding-atlas). ### Why Chainlink SVR? Traditional MEV searching depends on spotting oracle price updates in the public mempool. With SVR, new price reports are shared in a private mempool via Flashbots MEV-Share. Searchers place bids to include liquidation transactions directly **after** the price update in the same block. The highest bidder gets included, capturing the liquidation bonus while sharing part of the winning bid with the DeFi protocol (e.g., Aave) and the Chainlink Network. ### Example Application: Aave Liquidations Aave is a lending protocol where collateralized positions must remain above a [threshold](https://aave.com/help/borrowing/liquidations) called their health factor. When an oracle price update reveals a borrower is undercollateralized, the position becomes eligible for liquidation. The searcher liquidates the position in exchange for a liquidation bonus reward. With Chainlink SVR, a searcher's liquidation transaction can be bundled with the new SVR price update through Flashbots. For more background on how Aave liquidations work, see [Aave Documentation](https://docs.aave.com/). To understand how Chainlink SVR adds a structured auction for these Aave liquidation opportunities, read the following sections. ### Price Update, Auction, and Liquidation Execution Flow ![Image](/images/data-feed/svr/svr-price-update-auction-liquidation-flow.webp) ## Feed Aggregator Addresses > **NOTE** > > Searchers need to listen to the **Aggregator address** for price reports, not the proxy address. You can find the > aggregator address by reading `aggregator` in the proxy contract. For convenience, we've included the aggregator > address in the table below. Use **Aave-SVR** type feeds for Aave, **SVR** type feeds for other protocols, and > **SVR-Backup** for the legacy shared feeds. For the full list of SVR feeds, see the [SVR Feeds > page](/data-feeds/svr-feeds). ## How to Participate as a Searcher ### 1. High-Level Steps for Searchers 1. **Monitor and Identify SVR Price Updates**: Chainlink SVR sends oracle reports through Flashbots Protect (MEV-Share). Your searcher node must watch private transactions, not just the public mempool. It must also filter by these SVR data feed update events, and determine which feed address the update is for. 2. **Bundle and Bid** - Detect an eligible liquidation (e.g., an undercollateralized Aave position) triggered by the new price update. - Submit your liquidation transaction **in the same bundle** as the price update, placing a bid to entice block builders. 3. **Execute Liquidation**: If your bundle is selected, your liquidation happens **immediately after** the fresh price arrives onchain, capturing the liquidation bonus. Based on the results of the auction, a portion of the MEV is recaptured and split between Aave and the Chainlink Network, while the rest goes to you. ### 2. Listen for Events on MEV Share Chainlink SVR uses forwarder contracts to route feed updates. You typically see **one transaction per event**, which calls the function `forward(address to, bytes callData)` with the function selector `6fadcf72`. This method, in turn, calls the SVR aggregator's `transmitSecondary()` function with the new price data. 1. **Forwarder Contracts**: Each Node Operator Proxy has a unique forwarder contract. That means that feed update transactions may come from different forwarder addresses depending on which proxy sends the transaction. 2. **Function Selector**: In the transaction's `txs` array, look for the selector `6fadcf72` (`forward(...)`). 3. **callData**: The `callData` includes the parameters for `transmitSecondary()` on the aggregator contract. You can decode this if you need to extract the updated price when the onchain event is not emitted (details in the section below). Here's an example of a single transaction event: ```json { "hash": "0x_single_transaction_event_hash", "txs": [ { "to": "0x45ab36b69e02e59d3c49b863b31f530c991dd554", "functionSelector": "0x6fadcf72", "callData": "0x80640b.....................a2354d" } ] } ``` Below are code examples for setting up a listener that monitors the MEV-Share event stream for SVR feed updates. These examples show how to connect to the stream, filter for relevant transactions with the `6fadcf72` function selector, and process incoming events: ### TS ```ts import MevShareClient, { IPendingBundle, IPendingTransaction } from "@flashbots/mev-share-client" const mevShareClient = MevShareClient.useEthereumMainnet(authSigner) const txHandler = mevShareClient.on("transaction", async (tx: IPendingTransaction) => { if (tx.functionSelector === "6fadc72") { // Do something with the pending tx here. } }) // call before your program terminates: txHandler.close() bundleHandler.close() ``` ### Go ```go import "github.com/flashbots/mev-share-node/mevshare" req, err := http.NewRequestWithContext( ctx, "GET", "https://mev-share.flashbots.net", nil, ) // ... read the event data from the response var event mevshare.Hint err := json.Unmarshal(responseData, &event) // ... check if tx is calling the forward method if strings.Compare( strings.ToLower(tx.FunctionSelector.String()), "0x6fadcf72", ) != 0 { continue } ``` The event stream is also expected to occasionally transmit transaction bundles. The bundle event has a similar format to the single transaction event, but with more than one transactions. Transactions will be bundled in nonce ascending order, meaning txs[0] has the lowest nonce and txs[len(txs)] has the highest. Below is an example of a bundle: ```json { "hash": "0x_bundle_event_hash", "txs": [ { "to": "0x45ab36b69e02e59d3c49b863b31f530c991dd554", "functionSelector": "0x6fadcf72", "callData": "0x80640b.....................a2354d" }, { "to": "0x45ab36b69e02e59d3c49b863b31f530c991dd554", "functionSelector": "0x6fadcf72", "callData": "0x12340b.....................a2354d" } ] } ``` ### 3. Decode `callData` for `forward` and `transmitSecondary` Once you've identified a potential SVR feed update transaction, you'll need to decode its payload. This step allows you to extract: - The **feed address**: This is required to determine which SVR data feed is being updated. - The **new price data**: This is essential for determining if profitable liquidation opportunities exist. The decoding process involves understanding two function calls: 1. First, the transaction calls the `forward` function: ```solidity forward(address to, bytes callData) ``` 2. Inside the `callData` parameter is the encoded call to `transmitSecondary`, which contains the actual price data: ```solidity transmitSecondary( bytes32[3] calldata reportContext, bytes calldata report, bytes32[] calldata rs, bytes32[] calldata ss, bytes32 rawVs ) ``` The structure of `report` is: ```solidity struct Report { uint32 observationsTimestamp; bytes32 observers; int192[] observations; int192 juelsPerFeeCoin; } ``` Below is an example of an ABI definition for the `forward` and `transmitSecondary` function interfaces to help you decode the function calls: ```json [ { "type": "function", "name": "forward", "inputs": [ { "name": "to", "type": "address" }, { "name": "callData", "type": "bytes" } ], "outputs": [], "stateMutability": "nonpayable" }, { "type": "function", "name": "transmitSecondary", "inputs": [ { "name": "reportContext", "type": "bytes32[3]", "internalType": "bytes32[3]" }, { "name": "report", "type": "bytes", "internalType": "bytes" }, { "name": "rs", "type": "bytes32[]", "internalType": "bytes32[]" }, { "name": "ss", "type": "bytes32[]", "internalType": "bytes32[]" }, { "name": "rawVs", "type": "bytes32", "internalType": "bytes32" } ], "outputs": [], "stateMutability": "nonpayable" } ] ``` The following code samples demonstrate the complete decoding process, including extracting the median price from the nested report structure. These implementations show how to: 1. Decode the `forward` function call 2. Extract and decode the `transmitSecondary` function data 3. Parse the report bytes using the `Report` struct 4. Access the median observation (the updated price) ### TS ```ts const decodeInterface = new ethers.Interface(EXAMPLE_ABI) function decodeForwarderCall(callData: string) { const decodedForward = decodeInterface.decodeFunctionData("forward", callData) return { to: decodedForward[0], callData: decodedForward[1] } } function decodeTransmitSecondary(callData: string) { try { const decodedTransmitSecondary = decodeInterface.decodeFunctionData("transmitSecondary", callData) return { report: decodedTransmitSecondary[1] } } catch (error) { console.error("not transmit secondary call", error) return {} } } function decodeReport(reportRaw: string) { const decodedReport = ethers.AbiCoder.defaultAbiCoder().decode(["uint32", "bytes32", "int192[]", "int192"], reportRaw) return { observations: decodedReport[2] } } function updatedPrice(observations: bigint[]) { const medianObservation = observations[Math.floor(observations.length / 2)] return medianObservation } async function processTransaction(pendingTx: IPendingTransaction) { if (pendingTx.functionSelector !== CHAINLINK_PRICE_FEED_FUNCTION_SELECTOR) { return null } const { to, callData } = decodeForwarderCall(pendingTx.callData) const { report } = decodeTransmitSecondary(callData) if (report) { const { observations } = decodeReport(report) const newPrice = updatedPrice(observations) } // calculate health factors for affected users ... } ``` ### Go ```go decoded, err := hex.DecodeString(strings.TrimPrefix(strings.ToLower(tx.CallData.String()), "0x")) contractAddress := "0x" + strings.ToLower(strings.TrimLeft(hex.EncodeToString(decoded[4:36]), "0")) ff := strings.ToLower(strings.TrimLeft(hex.EncodeToString(decoded[100:104]), "0")) if strings.Compare(ff, "ba0cb29e") != 0 { return nil, fmt.Errorf("event doesn't invoke svr method: ba0cb29e, instead: %s", ff) } transmitSecondaryElems := make(map[string]interface{}) err := getTransmissionTypes().UnpackIntoMap(transmitSecondaryElems, decoded[104:]) report, exists := transmitSecondaryElems["report"] return MedianFromReport(report.([]byte)) func MedianFromReport(report []byte) (*big.Int, error) { reportElems := map[string]interface{}{} err := getReportTypes().UnpackIntoMap(reportElems, report) observationsIface, ok := reportElems["observations"] observations, ok := observationsIface.([]*big.Int) median := observations[len(observations)/2] return median, nil } func getReportTypes() abi.Arguments { return abi.Arguments([]abi.Argument{ {Name: "observationsTimestamp", Type: mustNewType("uint32")}, {Name: "rawObservers", Type: mustNewType("bytes32")}, {Name: "observations", Type: mustNewType("int192[]")}, {Name: "juelsPerFeeCoin", Type: mustNewType("int192")}, }) } func getTransmissionTypes() abi.Arguments { return abi.Arguments([]abi.Argument{ {Name: "reportContext", Type: mustNewType("bytes32[3]")}, {Name: "report", Type: mustNewType("bytes")}, {Name: "rs", Type: mustNewType("bytes32[]")}, {Name: "ss", Type: mustNewType("bytes32[]")}, {Name: "rawVs", Type: mustNewType("bytes32")}, }) } func mustNewType(t string) abi.Type { result, err := abi.NewType(t, "", nil) return result } ``` ### 4. Detect SVR-enabled Feeds When processing forward calls, verify that the `to` address from the code sample above (the destination of the forward call) matches one of the SVR feed aggregator addresses listed in the [Feed Aggregator Addresses](#feed-aggregator-addresses) section above. ### 5. Calculate Updated Price After successfully decoding the transaction data, you need to extract the median price from the `observations` array inside the decoded `report`: ```solidity report.observations[report.observations.length / 2] ``` This formula retrieves the median value from the sorted array of price observations. The middle element represents the median price. This price is the value committed to the blockchain that protocols will use for determining liquidation eligibility. To use this price effectively, convert it to the appropriate decimal representation for the asset pair. ### 6. Bidding With Flashbots MEV-Share Once you've detected an SVR update and identified a profitable liquidation opportunity, you need to construct and submit a bundle to Flashbots. Consider these key aspects when preparing your submission: - **Bundle Components**: Your bundle must contain two elements in this exact order: 1. The original oracle update transaction (referenced by its hash) 2. Your liquidation transaction (fully signed transaction bytes) - **Backrun Position**: MEV-Share only permits backruns, meaning your liquidation transaction must come after the target transaction. This order is enforced by the bundle structure. - **Bidding Strategy**: Block builders typically select the highest-paying bundle. Your bid must be competitive while leaving room for profit. The bundle's economic value comes from: - The gas price of your transaction - Any explicit payment you include to the builder **How to submit your bid**: Within your backrun transaction itself, send ETH to `block.coinbase` (the block builder's address). The higher the payment sent to `block.coinbase`, the more competitive your bundle becomes. Example: ```solidity // Within your liquidation transaction, send payment to block.coinbase payable(block.coinbase).transfer(bidAmount); ``` You pay your full bid amount to `block.coinbase`. The revenue split between Chainlink and the DeFi protocol occurs as part of the SVR system. - **Inclusion Parameters**: Specify the target block range for your bundle to be considered - Typically target the next block plus a small range for redundancy - A narrow block range can increase chances of inclusion but risks missing execution - **Authentication**: All Flashbots bundles require cryptographic signing with your wallet Below are code examples demonstrating how to construct and submit bundles to the Flashbots relay: ### TS ```ts async function sendBackrunBundle(pendingTx: IPendingTransaction) { const currentBlock = await provider.getBlockNumber() const signedBackrunTx = await buildDummyBackrunTx(pendingTx) const bundleParams: BundleParams = { inclusion: { block: currentBlock + 1, maxBlock: currentBlock + TARGET_BUNDLE_BLOCK_DELAY, }, body: [{ hash: pendingTx.hash }, { tx: signedBackrunTx, canRevert: false }], } const sendBundleResult = await mevShareClient.sendBundle(bundleParams) return sendBundleResult } async function buildDummyBackrunTx(pendingTx: IPendingTransaction) { const backrunTx = { data: {}, maxFeePerGas: 22000, maxPriorityFeePerGas: 22000 } const signedBackrunTx = await executorWallet.signTransaction(backrunTx) return signedBackrunTx } ``` ### Go ```go import "github.com/flashbots/mev-share-node/mevshare" // ... create and sign your transaction tx := types.NewTransaction(...) signedTx, err := types.SignTx(tx, ...) txBytes := signedTx.MarshalBinary() bundle := []mevshare.MevBundleBody { { Hash: &event.EventHash, }, { Tx: &txBytes, CanRevert: false, }, } params := mevshare.SendMevBundleArgs { Body: bundle, Inclusion: mevshare.MevBundleInclusion{blockNumber, blockNumber+1}, } byteParams, err := json.Marshal(params) body := []byte(fmt.Sprintf(`{"jsonrpc":"2.0","method":"mev_sendBundle","params":["%s"], "id":1}`, string(byteParams))) bodyReader := bytes.NewReader(body) postReq, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://relay.flashbots.net/", bodyReader) hashedBody := crypto.Keccak256Hash(body).Hex() signedMessage, err := crypto.Sign(accounts.TextHash([]byte(hashedBody)), privKey) postReq.Header.Add("X-Flashbots-signature", address.String()+":"+hexutil.Encode(signedMessage)) resp, err := http.DefaultClient.Do(postReq) ``` For gas management, simulations, and other advanced usage, see the official [Flashbots documentation](https://docs.flashbots.net/). Bidding for bundle events is very similar to single transaction events. As a searcher, you can append your transaction at the end of the bundle by using the bundle event hash just like you would with the single transaction event hash. Below is an example: ```json { "jsonrpc": "2.0", "method": "mev_sendBundle", "params": [ { "version": "v0.1", "inclusion": { "block": "0x01", "maxBlock": "0x04" }, "body": [ { "hash": "0x_bundle_event_hash" }, { "tx": "0x0213a...", "canRevert": false } ] } ] } ``` ### 7. Considerations - **Competition**: Multiple searchers might detect the same liquidation. The best bid typically wins. - **Profit-Sharing**: A portion of liquidation MEV is recaptured and redirected to the integrating DeFi protocol and the Chainlink Network. - **Gas Efficiency**: Bundles that are too costly might erode the profitability of the proposed transaction. Optimize carefully. - **Non-SVR `forward` calls**: There are other events on the MEV-Share event stream that have the same `forward` function signature. Be sure to filter for the ones that call `transmitSecondary` on the listed SVR feeds. ### 8. Next Steps for Searchers 1. **Set Up Your Searcher Node** - Integrate with [Flashbots MEV-Share](/data-feeds/svr-feeds/searcher-onboarding-ethereum) to read private transactions. - Filter for calls matching function selector `6fadcf72` directed at the correct contract address. 2. **Test Your Bundles** - Construct atomic bundles combining the price update and your liquidation transaction. - Try local simulations or testnet deployments where available. 3. **Stay Updated** - Keep track of any updates to the SVR aggregator address, function signatures, or Flashbots MEV-Share changes. - Monitor for changes in liquidation parameters (e.g., new assets, different collateral thresholds). --- # Base Tokenized Equities (Coinbase B20) Source: https://docs.chain.link/data-feeds/tokenized-equity-feeds/coinbase Chainlink provides tokenized equity feeds for Coinbase tokenized stocks on Base. Each tokenized stock is a B20 token, Base's native token standard for real-world assets, an extension of ERC-20, identified by its contract address rather than its ticker. The corresponding Chainlink feed reports the Total Return Value of the token by combining the underlying equity's market price with a multiplier read from Coinbase's on-chain oracle registry. These feeds use the standard Chainlink V3 aggregator interface (`latestRoundData()`), so any consumer reads the published price through the proxy in the same way as a crypto price feed. They are push-based oracles intended for compliance reference pricing, protocol integration, and similar use cases. ## Available Base tokenized equity feeds The following table shows the available Base tokenized equity feeds. ## Total Return Value calculation Base tokenized equity feeds calculate the token price using the following formula: ``` Token Price = Underlying Equity Market Price × Multiplier ``` Where: - **Underlying Equity Market Price**: Sourced from Chainlink's 24/5 equity price feeds, which aggregate data across regular, pre-market, post-market, and overnight trading sessions. - **Multiplier**: Read from Coinbase's on-chain oracle registry on Base, which returns the multiplier and pause state for each token in a single call. The multiplier accounts for dividend conversions and corporate action adjustments (see [Corporate action handling](#corporate-action-handling-and-oracle-pause) below). The on-chain oracle registry sources the multiplier from the B20 tokens. The multiplier is WAD-scaled (a fixed-point number with 18 decimals). One B20 token does not permanently equal one share: the multiplier is the redemption ratio between the token and the underlying equity. This approach ensures the token price reflects the total return of the underlying equity, including dividend conversions and corporate action adjustments, rather than just the current market price per share. ## How the multiplier works The multiplier accounts for events that change the relationship between token quantity and underlying equity value: | Event Type | Multiplier Behavior | Example | | :--------------------------- | :------------------------------ | :---------------------- | | Dividend (converted) | Small increase | Multiplier: 1.00 → 1.02 | | Stock split (10:1 example) | Large increase | Multiplier: 1.0 → 10.0 | | Reverse split (1:10 example) | Large decrease | Multiplier: 10.0 → 1.0 | | Spin-offs | Adjustment to reflect new value | Varies by event | Key behaviors of the B20 multiplier model: - **Cash dividends are converted to shares** of the underlying equity and reflected as a multiplier increase, rather than distributed as cash to the token holder. For example, after a dividend the multiplier may increase from 1.00 to 1.02, representing that 1 token is redeemable for 1.02 shares. - **Holder balances never change**. Corporate actions update the multiplier — there is no rebasing and there are no airdrops. A holder's raw token balance stays constant while its redemption value adjusts. - **Multiplier changes are observable on-chain**. `MultiplierUpdated` events record every change, and sensitive operations are wrapped in human-readable announcement events (`Announcement` / `EndAnnouncement`) that can be atomically bundled with the multiplier update they describe. ## Corporate action handling and oracle pause Corporate actions are coordinated by Coinbase as the asset issuer. Chainlink does not provide corporate-action calendar data or automated pause triggers; pause timing and multiplier updates are coordinated by Coinbase. Coinbase's oracle registry exposes the multiplier and a pause flag for each token, and the feed honors that pause flag: **Normal mode** (`paused == false`): The feed returns the current underlying equity market price multiplied by the current multiplier. **Paused mode** (`paused == true`): The feed stops publishing new prices and holds the last known good value. This prevents an inconsistent token price from being published while a corporate action is in progress and the underlying price and multiplier are temporarily out of sync. During a corporate action, the issuer-driven workflow keeps the token price continuous: 1. Coinbase pauses the oracle through the registry, freezing the feed at the last known good value. 2. After the corporate action takes effect and Coinbase confirms that both the underlying market price and the multiplier reflect the new values correctly, the registry is unpaused. 3. The feed resumes publishing using the updated multiplier. For example, during a 10:1 stock split: | Stage | Stock Price | Multiplier | Token Price | | :------------ | :---------- | :------------ | :------------ | | Before split | $200 | 1.0 | $200 | | During pause | Frozen | Pending: 10.0 | $200 (frozen) | | After unpause | $20 | 10.0 | $200 | The token price remains continuous at $200 throughout the event, even though the underlying equity market price dropped from $200 to $20. ## Example scenarios **Normal operation** ``` - Stock price: $200 → $201 - Multiplier: 1.00 (unchanged) - Token price: $201 ``` **Dividend conversion** (small multiplier increase) ``` - Stock price: $200 - Multiplier: 1.00 → 1.02 (cash dividend converted to shares) - Token price: $204 ``` **Stock split** (10:1, scheduled corporate action) ``` 1. Coinbase pauses the oracle through the registry; the feed freezes at the current token price 2. The corporate action executes, wrapped in on-chain announcement events 3. Market reopens with the underlying stock price at $20 (post-split) 4. Coinbase confirms alignment and unpauses the registry 5. Multiplier becomes 10.0, token price = $20 × 10 = $200 (continuous) ``` **Split with timing mismatch** (stock price updates before the multiplier) ``` - Registry paused; multiplier not yet updated - Underlying stock price = $20, multiplier still 1.0 - Result: Feed remains frozen at the pre-pause price until Coinbase unpauses the registry ``` This fail-safe behavior prevents incorrect token prices from being published when the underlying stock price and the multiplier are temporarily out of sync. ## Off-hours and session behavior Base tokenized equity feeds are configured as 24/5 tokenized equity feeds (regular, pre-market, post-market, and overnight sessions), where underlying liquidity and session data quality support it. Integrators should be aware of standard tokenized equity behavior: - **Off-hours / closed sessions**: When underlying equity markets are closed (weekends, holidays, thin overnight windows), the feed holds the last close even though the contract remains callable via `latestRoundData()`. These feeds do not have heartbeats during off-hours. - **Staleness checks**: Integrators should read `updatedAt` and implement staleness bounds appropriate to their use case. See [Tokenized Equity Feeds](/data-feeds/tokenized-equity-feeds) for the shared behavioral model across providers. --- # Tokenized Equity Feeds Source: https://docs.chain.link/data-feeds/tokenized-equity-feeds ## What are tokenized equities? Tokenized equities are blockchain tokens that represent ownership or economic exposure to traditional stocks and ETFs. These tokens are distinct financial instruments issued by token issuers, not the underlying equities themselves. Tokenized equity feeds deliver the **primary market price representing the intrinsic value of the equity token** – this is the calculated theoretical token value and may account for long-term total return adjustments such as dividends or corporate actions. ## About tokenized equity feeds Chainlink Tokenized Equity Feeds provide continuous 24/5 pricing for tokenized representations of US equities and ETFs. These feeds deliver the calculated primary market price of the token as determined by the issuer's pricing methodology, enabling DeFi protocols to accurately value tokenized equities across all major trading sessions in US markets, including regular hours, pre-market, post-market, and overnight (Eastern Standard Time). Tokenized equity feeds behave differently from standard crypto price feeds due to structural connections with underlying traditional equity markets affecting asset liquidity and pricing. Given the specialized nature of these feeds, all developers must reach out to Chainlink Labs prior to integrating these feeds and also review the [key differences](#key-differences-from-standard-feeds) and [risks](#risks) sections to understand how these characteristics may affect your application. See the [Provider Catalog](/data-feeds/tokenized-equity-feeds/providers) to view available feeds and issuer-specific implementations. ![Image](/images/data-feed/tokenized-equity/tokenized-equity-diagram.png) ## Why use tokenized equity feeds? Tokenized equities bring traditional stocks and ETFs onchain, but integrating them into DeFi protocols creates a fundamental challenge: US equity markets operate only 6.5 hours per trading day during regular hours, yet DeFi protocols operate continuously. Interest accrues around the clock, liquidation thresholds are monitored constantly, and risk parameters remain active regardless of underlying market hours. Chainlink Tokenized Equity Feeds help bridge this gap between TradFi and DeFi markets by: - **Providing continuous 24/5 pricing**: A single feed that spans regular, pre-market, post-market, and overnight sessions - **Simplifying integration**: Consume one feed across trading sessions instead of managing multiple session-specific data sources - **Applying session-aware smoothing**: Intelligent pricing algorithms that aim to reduce certain price anomalies during session transitions - **Maintaining benchmark accuracy**: Optimized algorithms that are designed to track consolidated tape benchmarks with high fidelity - **Performing tokenized price calculations**: Returning real-time calculated primary market valuations of tokenized equities based on the issuer's multiplier values. ## Available tokenized equity feeds The following table shows all available tokenized equity feeds. ## How tokenized equity feeds work Tokenized equity feeds leverage Chainlink's decentralized oracle infrastructure to aggregate price data from multiple trading sessions and apply session-aware smoothing algorithms. The feeds combine data from various trading venues into a single continuous 24/5 price, handling the complexity of multi-session equity markets. ### Session coverage US equity markets exhibit distinct liquidity regimes throughout the 24-hour cycle: | Session | Hours (ET) | Liquidity | Characteristics | | :-------------- | :-------------------- | :-------- | :-------------------------------------------- | | Pre-Market | 04:00 – 09:30 | Rising | Increasing activity as regular hours approach | | Regular Trading | 09:30 – 16:00 | Deep | Full liquidity, primary price discovery | | Post-Market | 16:00 – 20:00 | Declining | Reduced liquidity, wider spreads | | Overnight | 20:00 – 04:00 | Thin | Limited venues, potential for price spikes | | Weekend | Fri 20:00 – Sun 20:00 | Zero | Traditional markets closed | ### Session-aware smoothing To enable reliable, 24/5 continuous pricing, tokenized equity feeds apply intelligent smoothing during session transitions to target pricing integrity under different liquidity conditions. The smoothing methodology: 1. **Tracks benchmark prices accurately** during liquid regular sessions 2. **Filters microstructure noise** during illiquid session transitions 3. **Reduces the risk of phantom liquidations** from transient price spikes in thin markets 4. **Converges quickly** after legitimate large price movements 5. **Minimizes tracking lag** to reduce mispricing during active trading Please note that while intelligent smoothing is designed to mitigate these issues, tokenized equities' structural connections with underlying traditional equity markets, liquidity, and pricing considerations require protocols to ensure performance of Chainlink Tokenized Equity Feeds matches expectations and is appropriate for the intended use case. ### Input sources Tokenized equity feeds combine multiple input sources: - **Regular hours data**: Multi-sourced from consolidated tape data with deep liquidity - **Extended hours data**: Pre-market and post-market venue data - **Overnight data**: Data from overnight trading venues *Extended and overnight session data may be sourced from fewer providers than regular hours data, reflecting the reduced number of venues operating during these periods. See the [Limited Provider Coverage](#limited-provider-coverage-during-extended-and-overnight-sessions) risk section for details.* ## Key differences from standard feeds While tokenized equity feeds use the same Chainlink infrastructure as standard crypto price feeds, they have distinct behaviors you should understand before integrating. ### Continuous vs. point-in-time pricing Standard crypto price feeds reflect a single aggregated market price at a point in time from 24/7 markets. Tokenized equity feeds combine data from multiple trading sessions that have different operating hours, update frequencies, liquidity profiles, and data sources. The feed value represents a calculated return using the best mid-price for continuous 24/5 coverage of underlying equity markets. ### Smoothing during transitions Standard feeds report raw aggregated prices. Tokenized equity feeds apply smoothing algorithms during session transitions in an effort to filter out microstructure noise and mitigate artificial volatility. This means: - Brief price spikes in illiquid sessions are dampened - Convergence to new price levels after gaps is gradual rather than instantaneous - The reported price may temporarily lag during rapid price movements For more information about this risk, see [Smoothing-induced Tracking Lag](#smoothing-induced-tracking-lag) below. ### Variable data quality by session The reliability and accuracy of tokenized equity feeds varies by trading session: | Session | Data Quality | Provider Coverage | Update Frequency | | :-------- | :----------- | :--------------------- | :--------------- | | Regular | Highest | Multiple providers | Highest | | Extended | Moderate | Limited providers | Moderate | | Overnight | Lower | Very limited | Lower | | Weekend | N/A | No traditional trading | Stale | ### Provider-specific behaviors Tokenized equity feeds for assets from different issuers may differ in terms of their behavior and composition. Review the provider-specific documentation in the [Provider Catalog](/data-feeds/tokenized-equity-feeds/providers) before integrating one of these feeds. *** ## Risks Tokenized equity feeds introduce specific risks related to the nature of traditional equity markets, multi-session data aggregation, smoothing algorithms, and data sourcing. Users must understand these factors and implement appropriate safeguards. ### Limited provider coverage during extended and overnight sessions Extended and overnight session price data is sourced from a more limited number of data providers compared to regular hours, making these sessions less reliable than regular hours feeds, which are aggregated from a broader set of providers. If providers experience downtime, technical failures, or connectivity disruption, the feed may flatline or report stale values. Such issues may lead to mispricing, failed liquidations, and potential bad debt accumulation. **Overnight and extended sessions** carry additional nuance: liquidity is thinner and fewer venues contribute data. In those conditions the feed may continue to update while surfacing **atypical numeric values**—including, in edge cases, **a reported price of zero** or other answers that are not economically meaningful as a reference price for the token. That behavior is distinct from staleness (fresh timestamps can accompany unsafe numbers). **Treat unexpected zeros, abrupt discontinuities, or values outside plausible bands as invalid for automated risk decisions** unless you independently confirm them against issuer methodology, recent observations, or supplementary market data. #### Mitigation - Implement staleness detection by monitoring the feed's last update timestamp - Define protocol behavior for when data stops updating (pause operations, use bounded trading ranges, or implement fallback logic) - Add **value-level validation**: enforce minimum and maximum bounds, compare against recent ticks for continuity, and specify explicit fallback behavior for **zero**, out-of-range answers, or sudden jumps during overnight windows - Consider restricting high-risk operations (large liquidations, new positions) during extended and overnight sessions ### Structural illiquidity during extended hours Pre-market, post-market, and overnight sessions are inherently less liquid than regular trading hours. This results in: - **Wider bid-ask spreads**: Higher transaction costs and less reliable mid-prices - **Stale ticks**: Longer intervals between price updates - **Price gaps**: Larger price movements on individual trades - **Higher volatility**: Increased susceptibility to large orders moving prices These conditions are inherent to the underlying markets, not the feed itself, but will be visible in the published data. #### Mitigation - Evaluate whether full 24/5 coverage is appropriate for your use case, or if restricting operations to regular hours is safer - Configure session-specific risk thresholds, circuit breakers, or mode switching aligned with your risk appetite - Apply additional safeguards (wider liquidation buffers, reduced leverage) during lower-liquidity sessions - Validate configurations during integration testing, not post-deployment ### Price jumps at session transitions When transitioning between sessions (Regular ↔ Extended ↔ Overnight), price dislocations can occur due to: - Different liquidity conditions between sessions - Different venues and participants per session - Accumulated order flow during closed periods - News events occurring outside regular hours - Shifts in data provider coverage between sessions (i.e., different cohorts of data providers contributing to the aggregated price) These are expected market microstructure effects, not data quality issues. Typical jumps are minor, but larger spikes are possible during low-liquidity environments or impactful news cycles. #### Mitigation - Implement transition-aware logic that expects and handles price gaps - Consider pausing or restricting operations during transition windows - Apply additional smoothing or price change limits during transitions - Use time-weighted averages (TWAP) or exponential moving averages (EMA) for risk-sensitive calculations ### Smoothing-induced tracking lag The session-aware smoothing that mitigates phantom liquidations also introduces tracking lag: a delay between market price movements and the reported feed price. During rapid legitimate price moves: - The smoothed price will temporarily trail the true market price - Convergence typically occurs within seconds to tens of seconds - In extreme cases, this may result in delayed liquidations or arbitrage opportunities #### Mitigation - Understand the trade-off: smoothing protects against false liquidations but delays real ones - Configure your protocol's risk parameters to account for potential tracking lag - Consider the tracking lag characteristics when setting liquidation thresholds - Monitor for persistent deviation between tokenized equity feeds and other market data sources ### Weekend and holiday behavior Traditional equity markets are closed on weekends and holidays. During these periods: - The feed will report the last onchain value published before the market closed, which may have occurred minutes to hours before the market close based on the deviation or heartbeat thresholds. It may not match the official exchange closing price of the underlying equity. - These feeds do not publish updates, including heartbeat updates, while markets are closed. The feed's timestamp reflects the final update published before the market closed. - No new price discovery occurs in the underlying markets - The staleness indicator will show increasingly old timestamps This reflects true underlying market inactivity rather than an outage or failure. #### Mitigation - Treat weekends and holidays as expected states requiring specific protocol behavior - Options include: pausing operations, allowing restricted trading within bounded ranges, or referencing tokenized asset prices on secondary markets - Incorporate authoritative exchange holiday calendars (NYSE/NASDAQ for US equities) into your integration - Define deterministic behaviors for these periods before deployment ### Corporate actions Traditional equities are subject to corporate actions that can dramatically change asset prices: - **Stock splits and reverse splits**: Can cause overnight price changes of 2x, 10x, or more - **Dividends**: Ex-dividend date adjustments affect pricing - **Mergers and acquisitions**: May cause trading halts or significant price changes - **Spin-offs**: New securities with separate pricing These actions are usually announced outside regular trading hours. The feed will reflect these price changes when markets reopen, which may appear as sudden large moves. #### Mitigation - Actively monitor corporate action announcements for assets in your protocol - Adjust pricing logic, risk parameters, and position limits around corporate action dates - Consider pausing markets during corporate action windows - Implement maximum price change limits with manual review for extreme moves #### Pause confirmation for corporate actions *This section contains information for asset issuers that provide Chainlink with pause state for tokenized equity feeds.* Asset issuers use pause-based workflows for large corporate actions such as stock splits or reverse splits. In these workflows, **the feed freezes at the last known good token value during the pause window**. Before signaling that the feed can be unpaused, the asset issuer should verify that the post-corporate-action equity price and the new issuer multiplier are aligned so the next published token value remains continuous. The underlying equity leg for this check should be the same Data Streams 24/5 US equity input that the feed uses for tokenized equity feed calculation. See the official list of the [24/5 equity streams](/data-streams/rwa-streams?show24x5=true\&page=1), and [learn more about how market data is sourced for tokenized equity feed calculation](/data-feeds/tokenized-equity-feeds#how-is-market-data-sourced-for-tokenized-equity-feed-calculation). For each tokenized equity feed, use the stream and time segment configured for that product (the value the feed pipeline would read as the stock leg for the current window), *not* an ad hoc quote from a different venue. 1. Take the new multiplier that is staged for go-live after the corporate action. 2. Read the current equity market price from that same Data Streams 24/5 input, for the appropriate session (regular, pre-market, post-market, or overnight), the same way the feed prices that window. 3. Multiply: `new multiplier` × that current equity price. That product is the implied token value the feed would publish if it unpaused *now*. 4. Compare that implied value to the token price at freeze: the last good, frozen feed value from when the pause took effect. They should match, or be treated as aligned only within the asset issuer's defined tolerance, because correct coordination means the post-corporate-action stock price and the new multiplier should combine to the same onchain token price as at the freeze, preserving continuity. - **If that equality holds** within the asset issuer's tolerance, the issuer can signal that the feed is ready to unpause according to the applicable release process and any other release gates. - **If it does not hold**, the equity input and the new multiplier are not yet consistent with each other after the corporate action, so unpausing the feed would jump or misstate the level for integrators. The feed should remain paused until the stock leg, multiplier, or both are reconciled and the check passes. ### Market closures and halts not explicitly flagged Tokenized equity feeds do not explicitly flag: - Exchange public holidays - Trading halts (regulatory, news-pending, circuit breakers) - Other operational closures During these periods, the feed may appear stale or flatline. This reflects true market inactivity, not a data quality issue. #### Mitigation - Implement staleness checks comparing the feed's last update timestamp to current time - Define protocol behavior for stale data scenarios - Monitor exchange halt notifications independently - Do not rely solely on feed freshness to determine market status. Users should incorporate external market status sources where available. *** ## Best practices ### Protocol design - **Set deviation limits**: Cap maximum acceptable price changes per update or per time window - **Define fallback behavior**: Determine what happens if the feed stops updating or deviates beyond thresholds - **Implement session awareness**: Consider different risk parameters for different trading sessions - **Test extensively**: Validate protocol behavior during session transitions, weekends, and simulated failure scenarios before production deployment ### Monitoring - **Track staleness**: Alert when no new updates arrive within expected frequency - **Monitor deviation**: Compare tokenized equity feed values against other market data sources when available - **Log session transitions**: Track behavior during session changes to identify patterns - **Set up alerts**: Notify operators of unusual price movements, extended staleness, or deviation from expected ranges ### Integration testing Before deploying to production: 1. Test behavior during each trading session (regular, extended, overnight) 2. Validate handling of session transitions 3. Simulate weekend and holiday behavior 4. Test failure scenarios (stale data, extreme price moves, data gaps) 5. Verify your staleness detection and fallback logic works correctly When using tokenized equity feeds, you inherit all responsibilities associated with standard price feeds plus additional responsibilities for understanding session-specific risks, equity market dynamics, and multi-source data quality. Review the [Developer Responsibilities](/data-feeds/developer-responsibilities) page for comprehensive guidance. *** ## FAQ ### How is market data sourced for tokenized equity feed calculation? Tokenized equity feeds use the [Data Streams v11 report schema](/data-streams/reference/report-schema-v11) for sourcing the underlying equity market price. ### How are total return values (stock splits, multipliers, market pauses) determined? These values are sourced from the asset issuer. Learn more in the [Provider Catalog](/data-feeds/tokenized-equity-feeds/providers). --- # Ondo Finance Global Markets (GM) Source: https://docs.chain.link/data-feeds/tokenized-equity-feeds/ondo Chainlink provides tokenized equity feeds for Ondo Finance Global Markets (GM) tokenized equities. These feeds differ from standard market-rate feeds in that they report **Total Return Values** rather than raw equity prices. ## Available Ondo tokenized equity feeds The following table shows the available Ondo tokenized equity feeds. ## Total Return Value calculation Ondo tokenized equity feeds calculate the token price using the following formula: ``` Token Price = Underlying Equity Market Price × Multiplier (sValue) ``` Where: - **Underlying Equity Market Price**: Sourced from Chainlink's 24/5 equity price feeds, which aggregate data across regular, pre-market, post-market, and overnight trading sessions - **Multiplier (`sValue`)**: Sourced from Ondo's `SyntheticSharesOracle` contract, which tracks dividend reinvestments and corporate action adjustments (see [Corporate action handling](#corporate-action-handling) below). This approach ensures that the token price reflects the total return of the underlying equity, including dividend reinvestments and corporate action adjustments, rather than just the current market price per share. ## How the multiplier works The multiplier (`sValue`) accounts for events that change the relationship between token quantity and underlying equity value: | Event Type | Multiplier Behavior | Example | | :--------------------------- | :------------------------------------ | :---------------------- | | Dividend reinvestment | Small increase (less than 1% per day) | `sValue`: 1.000 → 1.008 | | Stock split (10:1 example) | Large increase | `sValue`: 1.0 → 10.0 | | Reverse split (1:10 example) | Large decrease | `sValue`: 10.0 → 1.0 | | Spin-offs | Adjustment to reflect new value | Varies by event | The `SyntheticSharesOracle` contract enforces two update paths: - **Small updates** (≤1% per 24-hour period): Applied immediately via automated processes. These handle routine dividend reinvestments. - **Large updates** (>1%): Requires a scheduled pause window and manual confirmation. These handle major corporate actions like stock splits. ## Corporate action handling Large corporate actions require special handling to maintain price continuity. The system operates in two modes: **Normal mode**: The feed returns the current stock price multiplied by the current `sValue`. **Corporate action mode**: When a large corporate action is scheduled, the system enters a pause state where: 1. The pause is scheduled at least 24 hours in advance 2. At the scheduled pause time, the price freezes at the last known good value 3. The new `sValue` is staged but not yet applied 4. After the corporate action takes effect and Ondo confirms that both the stock price and `sValue` reflect the new values correctly, the system is manually unpaused 5. The new `sValue` takes effect, and normal operation resumes ![Image](/images/data-feed/tokenized-equity/ondo-tokenized-equity-pause-behavior.png) This pause mechanism ensures price continuity during corporate actions. For example, during a 10:1 stock split: | Stage | Stock Price | `sValue` | Token Price | | :------------ | :---------- | :------------ | :------------ | | Before split | $200 | 1.0 | $200 | | During pause | Frozen | Pending: 10.0 | $200 (frozen) | | After unpause | $20 | 10.0 | $200 | The token price remains continuous at $200 throughout the event, even though the underlying equity market price dropped from $200 to $20. > **NOTE** > > When the `SyntheticSharesOracle` contract returns `paused = true`, the Chainlink feed freezes at the last known good > price. Integrators should be aware that during pause windows: > > - The feed will not update until manually unpaused > - The pause duration depends on when Ondo confirms the corporate action has been correctly applied > - A minimum pause duration (at least 10 minutes) is enforced to prevent premature unpausing ## Example scenarios **Normal operation** ``` - Stock price: $200 → $201 - sValue: 1.000 (unchanged) - Token price: $201 ``` **Dividend reinvestment** (small `sValue` drift) ``` - Stock price: $200 - sValue: 1.000 → 1.008 (0.8% increase, within allowed drift) - Token price: $201.60 ``` **Stock split** (10:1, scheduled corporate action) ``` 1. Ondo schedules pause for Tuesday 8pm with `newSValue = 10` 2. At 8pm, feed freezes at current token price 3. Market opens Wednesday with stock price at $20 (post-split) 4. Ondo confirms alignment and unpauses 5. sValue becomes 10.0, token price = $20 × 10 = $200 (continuous) ``` **Split with timing mismatch** (stock price updates before `sValue`) ``` - Scheduled pause at Tuesday 8pm with `newSValue = 10` - At 8:01pm: stock price = $20, sValue = 1.0 (not yet updated) - Result: Feed remains frozen at the pre-pause price until Ondo confirms the sValue update and unpauses ``` This fail-safe behavior prevents incorrect token prices from being published when the stock price and `sValue` are temporarily out of sync. ## Smart Value Recapture (SVR) Primary mint and redeem for Ondo Finance Global Markets (GM) tokenized equities can be permissioned, but once the tokens are onchain, anyone can hold them and anyone can liquidate positions that use the feed. Most Ondo Finance Global Markets (GM) feeds have [SVR](/data-feeds/svr-feeds) enabled even when the primary market is not public to all participants. The liquidator set includes teams that can also access primary mint and redeem when they need to. Permissioned mint and redeem concerns the primary market; tokens can still circulate onchain and positions that use the feed can still be liquidated outside that path. For Oracle Extractable Value (OEV) and how SVR relates to standard feeds, use [SVR Feeds](/data-feeds/svr-feeds)—especially [Understanding MEV and OEV](/data-feeds/svr-feeds#understanding-mev-and-oev). To read answers from contracts, follow [Using Data Feeds](/data-feeds/using-data-feeds) and point at the SVR-enabled proxy address for your feed. --- # Asset Issuer Catalog Source: https://docs.chain.link/data-feeds/tokenized-equity-feeds/providers Tokenized equity feeds have issuer-specific implementations that affect pricing methodology, data handling, and operational behavior. This page catalogs the available issuers and their key characteristics. ## Available providers | Provider | Feed Type | Price Calculation | Corporate Action Handling | Documentation | Issuer Contact | | :----------- | :----------------- | :----------------------------------------------------- | :----------------------------------- | :----------------------------------------------------------- | :---------------------------------------- | | Ondo Finance | Total Return Value | Underlying Equity Market Price × Multiplier (`sValue`) | Pause-based with manual confirmation | [View details](/data-feeds/tokenized-equity-feeds/ondo) | [Contact page](https://ondo.finance/) | | Robinhood | Multiplier-scaled | Underlying Equity Market Price × `uiMultiplier` | Oracle pause on token contract | [View details](/data-feeds/tokenized-equity-feeds/robinhood) | [Contact page](https://robinhood.com/) | | Coinbase | Multiplier-scaled | Underlying Equity Market Price × Multiplier | Oracle pause on oracle registry | [View details](/data-feeds/tokenized-equity-feeds/coinbase) | [Contact page](https://www.coinbase.com/) | ## Provider-specific considerations Each provider may implement tokenized equity feeds differently based on their token design and corporate action handling requirements. Review the provider-specific documentation and contact Chainlink Labs at [chainlink_data_feeds@smartcontract.com](mailto:chainlink_data_feeds@smartcontract.com) before integrating their tokenized equity feeds into your protocol. --- # Robinhood Tokenized Equities Source: https://docs.chain.link/data-feeds/tokenized-equity-feeds/robinhood Chainlink provides tokenized equity feeds for Robinhood tokenized stocks. Each Robinhood tokenized stock is represented by its own ERC-20 token contract, deployed on Robinhood Chain (one contract per ticker). The corresponding Chainlink feed reports the Total Return Value of the token by combining the underlying equity's market price with a multiplier read directly from the Robinhood token contract, rather than the raw equity price. These feeds use the standard Chainlink V3 aggregator interface (`latestRoundData()`), so any consumer reads the published price through the proxy in the same way as a crypto price feed. They are push-based oracles intended for compliance reference pricing, protocol integration, and similar use cases. ## Available Robinhood tokenized equity feeds The following table shows the available Robinhood tokenized equity feeds. ## Total Return Value calculation Robinhood tokenized equity feeds calculate the token price using the following formula: ``` Token Price = Underlying Equity Market Price × Multiplier ``` Where: - **Underlying Equity Market Price**: Sourced from Chainlink's 24/5 equity price feeds, which aggregate data across regular, pre-market, post-market, and overnight trading sessions. - **Multiplier**: Read from the Robinhood token contract via the `uiMultiplier()` function. The multiplier accounts for dividend reinvestments and corporate action adjustments (see [Corporate action handling](#corporate-action-handling-and-oracle-pause) below). This approach ensures the token price reflects the total return of the underlying equity, including dividend reinvestments and corporate action adjustments, rather than just the current market price per share. ## How the multiplier works The multiplier accounts for events that change the relationship between token quantity and underlying equity value: | Event Type | Multiplier Behavior | Example | | :--------------------------- | :------------------------------ | :---------------------------- | | Dividend reinvestment | Small increase | `uiMultiplier`: 1.000 → 1.008 | | Stock split (10:1 example) | Large increase | `uiMultiplier`: 1.0 → 10.0 | | Reverse split (1:10 example) | Large decrease | `uiMultiplier`: 10.0 → 1.0 | | Spin-offs | Adjustment to reflect new value | Varies by event | Robinhood updates the multiplier on the token contract through two paths: - **Immediate update**: `updateMultiplier(uint256)` sets a new `uiMultiplier` effective immediately. - **Scheduled update**: `updateMultiplier(uint256, uint256 effectiveAt)` stages the next multiplier. The staged value is exposed through `newUIMultiplier()` and the activation time through `effectiveAt()`. The new value becomes the active `uiMultiplier` once the `effectiveAt` timestamp is reached. The `UIMultiplierUpdated` event (`oldMultiplier`, `newMultiplier`, `effectiveAtTimestamp`) is emitted whenever the multiplier changes or a new value is staged. Robinhood's on-chain token contract enforces two update paths: - **Small updates** (no price discontinuity): Applied immediately via automated processes. These handle routine dividend reinvestments. - **Large updates** (price discontinuity): Requires a scheduled pause window and manual confirmation before unpause. These handle major corporate actions like stock splits. ## Corporate action handling and oracle pause Corporate actions are coordinated by Robinhood as the asset issuer. Chainlink does not provide corporate-action calendar data or automated pause triggers; pause timing and multiplier updates are coordinated by Robinhood. The Robinhood token contract exposes a dedicated oracle pause flag, `oraclePaused()`, that the feed honors: **Normal mode** (`oraclePaused() == false`): The feed returns the current underlying equity market price multiplied by the current `uiMultiplier`. **Paused mode** (`oraclePaused() == true`): The feed stops publishing new prices and holds the last known good value. This prevents an inconsistent token price from being published while a corporate action is in progress and the underlying price and multiplier are temporarily out of sync. During a corporate action, the issuer-driven workflow keeps the token price continuous: 1. Robinhood pauses the oracle (`pauseOracle()`), freezing the feed at the last known good value. 2. The new multiplier is staged via `updateMultiplier(newMultiplier, effectiveAt)` (or applied at the scheduled time). 3. After the corporate action takes effect and Robinhood confirms that both the underlying market price and the multiplier reflect the new values correctly, the oracle is unpaused (`unpauseOracle()`). 4. The feed resumes publishing using the updated multiplier. For example, during a 10:1 stock split: | Stage | Stock Price | `uiMultiplier` | Token Price | | :------------ | :---------- | :------------- | :------------ | | Before split | $200 | 1.0 | $200 | | During pause | Frozen | Pending: 10.0 | $200 (frozen) | | After unpause | $20 | 10.0 | $200 | The token price remains continuous at $200 throughout the event, even though the underlying equity market price dropped from $200 to $20. ## Example scenarios **Normal operation** ``` - Stock price: $200 → $201 - uiMultiplier: 1.000 (unchanged) - Token price: $201 ``` **Dividend reinvestment** (small multiplier drift) ``` - Stock price: $200 - uiMultiplier: 1.000 → 1.008 - Token price: $201.60 ``` **Stock split** (10:1, scheduled corporate action) ``` 1. Robinhood pauses the oracle and stages newUIMultiplier = 10 with an effectiveAt timestamp 2. The feed freezes at the current token price while oraclePaused() is true 3. Market reopens with the underlying stock price at $20 (post-split) 4. Robinhood confirms alignment and calls unpauseOracle() 5. uiMultiplier becomes 10.0, token price = $20 × 10 = $200 (continuous) ``` **Split with timing mismatch** (stock price updates before the multiplier) ``` - Oracle paused with newUIMultiplier = 10 staged - Underlying stock price = $20, uiMultiplier still 1.0 (not yet effective) - Result: Feed remains frozen at the pre-pause price until Robinhood unpauses the oracle ``` This fail-safe behavior prevents incorrect token prices from being published when the underlying stock price and the multiplier are temporarily out of sync. ## Off-hours and session behavior Robinhood tokenized equity feeds are configured as 24/5 tokenized equity feeds (regular, pre-market, post-market, and overnight sessions), where underlying liquidity and session data quality support it. Integrators should be aware of standard tokenized equity behavior: - **Off-hours / closed sessions**: When underlying equity markets are closed (weekends, holidays, thin overnight windows), the feed may hold the last published price even though the contract remains callable via `latestRoundData()`. These feeds do not have heartbeats during off-hours. - **Staleness checks**: Integrators should read `updatedAt` and implement staleness bounds appropriate to their use case. See [Tokenized Equity Feeds](/data-feeds/tokenized-equity-feeds) for the shared behavioral model across providers. ## Smart Value Recapture (SVR) Primary mint and redeem for Robinhood tokenized equities can be permissioned, but once the tokens are onchain, anyone can hold them and anyone can liquidate positions that use the feed. Robinhood feeds have SVR enabled even when the primary market is not public to all participants. The liquidator set includes teams that can also access primary mint and redeem when they need to. Permissioned mint and redeem concerns the primary market; tokens can still circulate onchain and positions that use the feed can still be liquidated outside that path. For Oracle Extractable Value (OEV) and how SVR relates to standard feeds, use [SVR Feeds](/data-feeds/svr-feeds)—especially [Understanding MEV and OEV](/data-feeds/svr-feeds#understanding-mev-and-oev). To read answers from contracts, follow [Using Data Feeds](/data-feeds/using-data-feeds) and point at the proxy address for your feed. --- # Using Data Feeds on Tron Source: https://docs.chain.link/data-feeds/tron This tutorial demonstrates how to read Chainlink Data Feeds on the TRON blockchain. You will learn how to deploy a smart contract on TRON Nile Testnet using [TronBox](https://tronbox.io/) and interact with it offchain using [TronWeb](https://tronweb.network/). Chainlink Data Feeds on TRON provide reliable, decentralized price data for your applications. This tutorial covers: 1. **Smart Contract Development**: Creating and deploying a contract that reads price feeds 2. **Offchain Interaction**: Using TronWeb to interact with your deployed contract By the end of this tutorial, you will have a working smart contract that reads real-time price data and a JavaScript application that displays the results. ## Prerequisites Before you begin, ensure you have: - **Knowledge of JavaScript and Solidity** - **Node.js 20 or higher** - [Install the latest release](https://nodejs.org/en/download/). Optionally, use [nvm](https://github.com/nvm-sh/nvm/blob/master/README.md) to switch between Node.js versions with nvm use 20 ```bash $ node -v v20.11.0 ``` - **TronBox** (version 3.3 or higher) - Install globally with npm install -g tronbox Verify your installation using tronbox version: ```bash $ tronbox version Tronbox v4.2.0 Solidity v0.8.23 (tron-solc) ``` - **npm** package manager - **A TRON compatible Web3 wallet** (e.g., [TronLink](https://www.tronlink.org/)) ### Setting Up Your TRON Wallet 1. Install the TronLink browser extension 2. Create a new wallet or import an existing one 3. Export your private key (you will need this for deploying your smart contract): - Open TronLink wallet extension - Open the **Wallet Management** menu - Select **Export Account** - Select **Private Key** - Enter your password - Copy your private key > **CAUTION: Private Key Security** > > Never share your private key or commit it to version control. Store it securely and only use testnet accounts for > development. ### Getting Test Tokens Fund your wallet with test TRX from the TRON Nile Testnet faucet: 1. Visit [https://nileex.io/join/getJoinPage](https://nileex.io/join/getJoinPage) 2. Enter your TRON wallet address 3. Complete the verification and receive 2000 test TRX ## Setup ### Clone the Example Repository ```bash git clone https://github.com/smartcontractkit/smart-contract-examples.git && cd smart-contract-examples/data-feeds/tron/getting-started ``` ### Install Dependencies ```bash npm install ``` ### Configure Environment Create a `.env` file by copying the sample: ```bash cp .env.example .env ``` Edit the `.env` file and add your private key: ```bash export PRIVATE_KEY_NILE=your_nile_testnet_private_key_here ``` ## Part 1: Smart Contract Development and Deployment ### Understanding the Smart Contract The `DataFeedReader.sol` contract provides four key functions to interact with Chainlink Data Feeds on TRON: - **`getChainlinkDataFeedLatestAnswer()`**: Returns complete round data including price, timestamps, and round IDs - **`getLatestPrice()`**: Returns only the current price for simplified usage - **`getDecimals()`**: Returns the number of decimal places for proper price formatting - **`getDescription()`**: Returns human-readable feed description (e.g., "BTC / USD") The contract uses the [`AggregatorV3Interface`](/data-feeds/api-reference#aggregatorv3interface) to interact with Chainlink data feeds. **View the complete contract code**: [DataFeedReader.sol](https://github.com/smartcontractkit/smart-contract-examples/blob/32bb95e558bdd6a4ebab4b6c1eadbfc83668c539/data-feeds/tron/getting-started/contracts/DataFeedReader.sol) ### Price Feeds Used in This Tutorial This tutorial uses the following price feeds on TRON Nile Testnet: | Asset Pair | Contract Address | | ---------- | ------------------------------------ | | BTC/USD | `TD3hrfAtPcnkLSsRh4UTgjXBo6KyRfT1AR` | | ETH/USD | `TYaLVmqGzz33ghKEMTdC64dUnde5LZc6Y3` | > **NOTE: More Price Feeds Available** > > These are just two examples for the tutorial. For a complete list of all available price feeds on TRON, visit the > [Price Feed Addresses](/data-feeds/price-feeds/addresses?network=tron) page. ### Compile the Contract ```bash tronbox compile ``` ### Deploy to TRON Nile Testnet Load your environment variables and deploy: ```bash source .env && tronbox migrate --network nile ``` After successful deployment, you will see output similar to: ``` Deploying 'DataFeedReader' ... DataFeedReader: TTZEzaRUfrSm2ENfkhrPzk5mMEkZVwS3eD ``` > **NOTE: Save Your Contract Address** > > Copy the deployed contract address - you will need it for the offchain interaction in Part 2. ## Part 2: Offchain Interaction with TronWeb ### Understanding the TronWeb Script The `reader.js` script demonstrates how to interact with your deployed contract using [TronWeb](https://tronweb.network/). The script provides several key features: - **TronWeb Configuration**: Connects to TRON Nile Testnet with proper endpoints - **Contract Interaction**: Calls all four functions from your deployed `DataFeedReader` contract - **Data Formatting**: Handles BigInt values and formats prices with proper decimals - **Error Handling**: Comprehensive error handling and troubleshooting guidance - **Multiple Examples**: Shows both detailed and simplified price reading patterns The script includes helper functions for formatting timestamps, prices, and round IDs, plus demonstrates reading from multiple price feeds with structured console output. **View the complete script code**: [reader.js](https://github.com/smartcontractkit/smart-contract-examples/blob/32bb95e558bdd6a4ebab4b6c1eadbfc83668c539/data-feeds/tron/getting-started/offchain/reader.js) ### Update the Contract Address Edit `offchain/reader.js` and replace `YOUR_DEPLOYED_CONTRACT_ADDRESS_HERE` with your actual contract address from the deployment step. ### Run the Price Reader ``` node offchain/reader.js ``` ### Expected Output When you run the script, you should see output similar to: ``` 🚀 Starting Chainlink Data Feed Reader 🌐 Network: TRON Nile Testnet 📋 Contract: TTZEzaRUfrSm2ENfkhrPzk5mMEkZVwS3eD ════════════════════════════════════════════════════════════ 🔍 Reading BTC/USD Price Feed Data... 📍 Feed Address: TD3hrfAtPcnkLSsRh4UTgjXBo6KyRfT1AR ────────────────────────────────────────────────── 📊 BTC / USD 💰 Current Price: $104,569.43 🔢 Raw Price Value: 10456943400942 📏 Decimals: 8 🆔 Round ID: 18446744073709556842 🕐 Started At: 6/4/2025, 9:00:21 AM 🕒 Updated At: 6/4/2025, 9:00:24 AM ✅ Answered In Round: 18446744073709556842 ⏰ Data Age: 65 minutes ago 🔍 Reading ETH/USD Price Feed Data... 📍 Feed Address: TYaLVmqGzz33ghKEMTdC64dUnde5LZc6Y3 ────────────────────────────────────────────────── 📊 ETH / USD 💰 Current Price: $2,638.74 🔢 Raw Price Value: 263874000000 📏 Decimals: 8 🆔 Round ID: 18446744073709564831 🕐 Started At: 6/4/2025, 9:58:19 AM 🕒 Updated At: 6/4/2025, 9:58:24 AM ✅ Answered In Round: 18446744073709564831 ⏰ Data Age: 7 minutes ago ``` ## Understanding the Results Each price feed returns several important pieces of data: - **Current Price**: The formatted price in USD - **Raw Price Value**: The unformatted price value from the contract - **Decimals**: Number of decimal places to properly format the price - **Round ID**: Unique identifier for this price update - **Timestamps**: When the price round started and was last updated - **Data Age**: How long ago the price was last updated ## Next Steps ### Explore More Data Feeds Now that you have successfully deployed and interacted with Chainlink Data Feeds on TRON, you can: - **Browse All Available Feeds**: Check out the complete list of [price feeds available on TRON](/data-feeds/price-feeds/addresses?network=tron) - **Access Historical Data**: Getting the latest price is not the only data that aggregators can retrieve. You can also retrieve historical price data. To learn more, see the [Historical Price Data](/data-feeds/historical-data) page. --- # U.S. Government Macroeconomic Data Feeds Source: https://docs.chain.link/data-feeds/us-government-macroeconomic/addresses Live values such as feed contract addresses are not inlined here. Wait for the official API to obtain current data. --- # Using Data Feeds on EVM Chains Source: https://docs.chain.link/data-feeds/using-data-feeds The code for reading Data Feeds is the same across all EVM-compatible blockchains and Data Feed types. You choose different types of feeds for different uses, but the request and response format are the same. To read a feed, specify the following variables: - **RPC endpoint URL:** This determines which network that your smart contracts will run on. You can use a [node provider service](https://ethereum.org/en/developers/docs/nodes-and-clients/nodes-as-a-service/) or point to your own [client](https://ethereum.org/en/developers/docs/nodes-and-clients/). If you are using a Web3 wallet, it is already configured with the RPC endpoints for several networks and the [Remix IDE](https://remix-project.org/) will automatically detect them for you. - **LINK token contract address:** The address for the LINK token contract is different for each network. You can find the full list of addresses for all supported networks on the [LINK Token Contracts](/resources/link-token-contracts?parent=dataFeeds) page. - **Feed contract address:** This determines which data feed your smart contract will read. Contract addresses are different for each network. You can find the available contract addresses on the following pages: - [Price Feed Addresses](/data-feeds/price-feeds/addresses) - [SmartData Addresses](/data-feeds/smartdata/addresses) The examples in this document indicate these variables, but you can modify the examples to work on different networks and read different feeds. This guide shows example code that reads data feeds using the following languages: - Onchain consumer contracts: - [Solidity](#solidity) - [Vyper](#vyper) - Offchain reads using Web3 packages: - [Javascript](#javascript) with [web3.js](https://web3js.readthedocs.io/) - [Python](#python) with [Web3.py](https://web3py.readthedocs.io/en/stable/) - [Golang](#golang) with [go-ethereum](https://github.com/ethereum/go-ethereum) ## Reading data feeds onchain These code examples demonstrate how to deploy a consumer contract onchain that reads a data feed and stores the value. ### Solidity To consume price data, your smart contract should reference [`AggregatorV3Interface`](https://github.com/smartcontractkit/chainlink/blob/contracts-v1.3.0/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol), which defines the external functions implemented by Data Feeds. ```sol // SPDX-License-Identifier: MIT pragma solidity ^0.8.7; import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol"; /** * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED * VALUES FOR CLARITY. * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE. * DO NOT USE THIS CODE IN PRODUCTION. */ /** * If you are reading data feeds on L2 networks, you must * check the latest answer from the L2 Sequencer Uptime * Feed to ensure that the data is accurate in the event * of an L2 sequencer outage. See the * https://docs.chain.link/data-feeds/l2-sequencer-feeds * page for details. */ contract DataConsumerV3 { AggregatorV3Interface internal dataFeed; /** * Network: Sepolia * Aggregator: BTC/USD * Address: 0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43 */ constructor() { dataFeed = AggregatorV3Interface(0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43); } /** * Returns the latest answer. */ function getChainlinkDataFeedLatestAnswer() public view returns (int256) { // prettier-ignore ( /* uint80 roundId */ , int256 answer, /*uint256 startedAt*/ , /*uint256 updatedAt*/ , /*uint80 answeredInRound*/ ) = dataFeed.latestRoundData(); return answer; } } ``` The `latestRoundData` function returns five values representing information about the latest price data. See the [Data Feeds API Reference](/data-feeds/api-reference) for more details. ### Vyper To consume price data, your smart contract should import `AggregatorV3Interface` which defines the external functions implemented by Data Feeds. You can find it [here](https://github.com/smartcontractkit/apeworx-starter-kit/blob/main/contracts/interfaces/AggregatorV3Interface.vy). You can find a `PriceConsumer` example [here](https://github.com/smartcontractkit/apeworx-starter-kit/blob/main/contracts/PriceConsumer.vy). Read the ***apeworx-starter-kit*** [README](https://github.com/smartcontractkit/apeworx-starter-kit) to learn how to run the example. ## Reading data feeds offchain These code examples demonstrate how to read data feeds directly off chain using Web3 packages for each language. ### Javascript This example uses [web3.js](https://web3js.readthedocs.io/) to retrieve feed data from the [BTC / USD feed](https://sepolia.etherscan.io/address/0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43) on the Sepolia testnet. ### web3.js ```js /** * THIS IS EXAMPLE CODE THAT USES HARDCODED VALUES FOR CLARITY. * THIS IS EXAMPLE CODE THAT USES UN-AUDITED CODE. * DO NOT USE THIS CODE IN PRODUCTION. */ const Web3 = require("web3") // for nodejs only const web3 = new Web3("https://rpc.ankr.com/eth_sepolia") const aggregatorV3InterfaceABI = [ { inputs: [], name: "decimals", outputs: [{ internalType: "uint8", name: "", type: "uint8" }], stateMutability: "view", type: "function", }, { inputs: [], name: "description", outputs: [{ internalType: "string", name: "", type: "string" }], stateMutability: "view", type: "function", }, { inputs: [{ internalType: "uint80", name: "_roundId", type: "uint80" }], name: "getRoundData", outputs: [ { internalType: "uint80", name: "roundId", type: "uint80" }, { internalType: "int256", name: "answer", type: "int256" }, { internalType: "uint256", name: "startedAt", type: "uint256" }, { internalType: "uint256", name: "updatedAt", type: "uint256" }, { internalType: "uint80", name: "answeredInRound", type: "uint80" }, ], stateMutability: "view", type: "function", }, { inputs: [], name: "latestRoundData", outputs: [ { internalType: "uint80", name: "roundId", type: "uint80" }, { internalType: "int256", name: "answer", type: "int256" }, { internalType: "uint256", name: "startedAt", type: "uint256" }, { internalType: "uint256", name: "updatedAt", type: "uint256" }, { internalType: "uint80", name: "answeredInRound", type: "uint80" }, ], stateMutability: "view", type: "function", }, { inputs: [], name: "version", outputs: [{ internalType: "uint256", name: "", type: "uint256" }], stateMutability: "view", type: "function", }, ] const addr = "0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43" const priceFeed = new web3.eth.Contract(aggregatorV3InterfaceABI, addr) priceFeed.methods .latestRoundData() .call() .then((roundData) => { // Do something with roundData console.log("Latest Round Data", roundData) }) ``` ### ethers.js ```js /** * THIS IS EXAMPLE CODE THAT USES HARDCODED VALUES FOR CLARITY. * THIS IS EXAMPLE CODE THAT USES UN-AUDITED CODE. * DO NOT USE THIS CODE IN PRODUCTION. */ const { ethers } = require("ethers") // for nodejs only const provider = new ethers.providers.JsonRpcProvider("https://rpc.ankr.com/eth_sepolia") const aggregatorV3InterfaceABI = [ { inputs: [], name: "decimals", outputs: [{ internalType: "uint8", name: "", type: "uint8" }], stateMutability: "view", type: "function", }, { inputs: [], name: "description", outputs: [{ internalType: "string", name: "", type: "string" }], stateMutability: "view", type: "function", }, { inputs: [{ internalType: "uint80", name: "_roundId", type: "uint80" }], name: "getRoundData", outputs: [ { internalType: "uint80", name: "roundId", type: "uint80" }, { internalType: "int256", name: "answer", type: "int256" }, { internalType: "uint256", name: "startedAt", type: "uint256" }, { internalType: "uint256", name: "updatedAt", type: "uint256" }, { internalType: "uint80", name: "answeredInRound", type: "uint80" }, ], stateMutability: "view", type: "function", }, { inputs: [], name: "latestRoundData", outputs: [ { internalType: "uint80", name: "roundId", type: "uint80" }, { internalType: "int256", name: "answer", type: "int256" }, { internalType: "uint256", name: "startedAt", type: "uint256" }, { internalType: "uint256", name: "updatedAt", type: "uint256" }, { internalType: "uint80", name: "answeredInRound", type: "uint80" }, ], stateMutability: "view", type: "function", }, { inputs: [], name: "version", outputs: [{ internalType: "uint256", name: "", type: "uint256" }], stateMutability: "view", type: "function", }, ] const addr = "0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43" const priceFeed = new ethers.Contract(addr, aggregatorV3InterfaceABI, provider) priceFeed.latestRoundData().then((roundData) => { // Do something with roundData console.log("Latest Round Data", roundData) }) ``` ### Python This example uses [Web3.py](https://web3py.readthedocs.io/en/stable/) to retrieve feed data from the [BTC / USD feed](https://sepolia.etherscan.io/address/0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43) on the Sepolia testnet. ```py # THIS IS EXAMPLE CODE THAT USES HARDCODED VALUES FOR CLARITY. # THIS IS EXAMPLE CODE THAT USES UN-AUDITED CODE. # DO NOT USE THIS CODE IN PRODUCTION. from web3 import Web3 # Change this to use your own RPC URL web3 = Web3(Web3.HTTPProvider('https://rpc.ankr.com/eth_sepolia')) # AggregatorV3Interface ABI abi = '[{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"description","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint80","name":"_roundId","type":"uint80"}],"name":"getRoundData","outputs":[{"internalType":"uint80","name":"roundId","type":"uint80"},{"internalType":"int256","name":"answer","type":"int256"},{"internalType":"uint256","name":"startedAt","type":"uint256"},{"internalType":"uint256","name":"updatedAt","type":"uint256"},{"internalType":"uint80","name":"answeredInRound","type":"uint80"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestRoundData","outputs":[{"internalType":"uint80","name":"roundId","type":"uint80"},{"internalType":"int256","name":"answer","type":"int256"},{"internalType":"uint256","name":"startedAt","type":"uint256"},{"internalType":"uint256","name":"updatedAt","type":"uint256"},{"internalType":"uint80","name":"answeredInRound","type":"uint80"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"version","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]' # Price Feed address addr = '0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43' # Set up contract instance contract = web3.eth.contract(address=addr, abi=abi) # Make call to latestRoundData() latestData = contract.functions.latestRoundData().call() print(latestData) ``` ### Golang You can find an example with all the source files [here](https://github.com/smartcontractkit/smart-contract-examples/tree/main/pricefeed-golang). This example uses [go-ethereum](https://github.com/ethereum/go-ethereum) to retrieve feed data from the [BTC / USD feed](https://sepolia.etherscan.io/address/0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43) on the Sepolia testnet. To learn how to run the example, see the [README](https://github.com/smartcontractkit/smart-contract-examples/blob/main/pricefeed-golang/README.md). ## Getting a different price denomination Chainlink Data Feeds can be used in combination to derive denominated price pairs in other currencies. If you require a denomination other than what is provided, you can use two data feeds to derive the pair that you need. For example, if you needed a BTC / EUR price, you could take the BTC / USD feed and the EUR / USD feed and derive BTC / EUR using division. ![Request Model Diagram](/images/price-feed-conversion-equation.gif) > **CAUTION: Important** > > If your contracts require Solidity versions that are `>=0.6.0 <0.8.0`, use [OpenZeppelin's SafeMath version 3.4](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.4/contracts/math/SafeMath.sol). ```sol // SPDX-License-Identifier: MIT pragma solidity ^0.8.7; import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol"; /** * Network: Sepolia * Base: BTC/USD * Base Address: 0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43 * Quote: EUR/USD * Quote Address: 0x1a81afB8146aeFfCFc5E50e8479e826E7D55b910 * Decimals: 8 */ /** * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY. * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE. * DO NOT USE THIS CODE IN PRODUCTION. */ contract PriceConverter { function getDerivedPrice( address _base, address _quote, uint8 _decimals ) public view returns (int256) { require(_decimals > uint8(0) && _decimals <= uint8(18), "Invalid _decimals"); int256 decimals = int256(10 ** uint256(_decimals)); (, int256 basePrice,,,) = AggregatorV3Interface(_base).latestRoundData(); uint8 baseDecimals = AggregatorV3Interface(_base).decimals(); basePrice = scalePrice(basePrice, baseDecimals, _decimals); (, int256 quotePrice,,,) = AggregatorV3Interface(_quote).latestRoundData(); uint8 quoteDecimals = AggregatorV3Interface(_quote).decimals(); quotePrice = scalePrice(quotePrice, quoteDecimals, _decimals); return (basePrice * decimals) / quotePrice; } function scalePrice( int256 _price, uint8 _priceDecimals, uint8 _decimals ) internal pure returns (int256) { if (_priceDecimals < _decimals) { return _price * int256(10 ** uint256(_decimals - _priceDecimals)); } else if (_priceDecimals > _decimals) { return _price / int256(10 ** uint256(_priceDecimals - _decimals)); } return _price; } } ``` ## More aggregator functions Getting the latest price is not the only data that aggregators can retrieve. You can also retrieve historical price data. To learn more, see the [Historical Price Data](/data-feeds/historical-data) page.