Search
The most common use case for Data Feeds is to Get the Latest Price. However, AggregatorV3Interface
also exposes functions which can be used to retrieve price data of a previous round ID.
There are two parameters that can cause Chainlink nodes to update:
Name | Description |
---|---|
Deviation Threshold | Chainlink nodes are monitoring prices of assets off-chain. The deviation of the real-world price of an asset beyond a certain interval triggers all the nodes to update. |
Heartbeat Threshold | If the price stays within the deviation parameters, it will only trigger an update every X minutes / hours. |
You can find these parameters at data.chain.link on an example like ETH / USD.
To learn how data feeds update, see the Decentralized Data Model page.
Data Feeds are updated in rounds. Rounds are identified by their roundId
, which increases with each new round. The increase might not be monotonic. Knowing the roundId
of a previous round allows contracts to consume historical price data.
The updatedAt
data feed property is the timestamp of an answered round and answeredInRound
is the round it was updated in. You can check answeredInRound
against the current roundId
. If answeredInRound
is less than roundId
, the answer is being carried over. If answeredInRound
is equal to roundId
, then the answer is fresh.
Important
A read can revert if the caller is requesting details of a round that was invalid or has not yet been answered yet. If you are deriving a round id without having observed it before, the round may or may not be complete. To check if it is, you must validate that the timestamp on that round is not 0.
In a best-case scenario rounds update chronologically. However, a round can time out if it doesn't reach consensus. Technically, that is a timed out round that carries over the answer from the previous round. The roundId
can jump significantly when the phaseId
is updated because of how the combination of phaseId
and roundId
is stored in the proxy.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
contract HistoricalPriceConsumerV3 {
AggregatorV3Interface internal priceFeed;
/**
* Network: Kovan
* Aggregator: ETH/USD
* Address: 0x9326BFA02ADD2366b30bacB125260Af641031331
*/
constructor() {
priceFeed = AggregatorV3Interface(0x9326BFA02ADD2366b30bacB125260Af641031331);
}
/**
* Returns historical price 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: 18446744073709562301
*
* @dev A timestamp with zero value means the round is not complete and should not be used.
*/
function getHistoricalPrice(uint80 roundId) public view returns (int256) {
(
uint80 id,
int price,
uint startedAt,
uint timeStamp,
uint80 answeredInRound
) = priceFeed.getRoundData(roundId);
require(timeStamp > 0, "Round not complete");
return price;
}
}
const Web3 = require("web3") // for nodejs only
const web3 = new Web3("https://kovan.infura.io/v3/<infura_project_id>")
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 = "0x9326BFA02ADD2366b30bacB125260Af641031331"
const priceFeed = new web3.eth.Contract(aggregatorV3InterfaceABI, addr)
// Valid roundId must be known. They are NOT incremental.
let validId = BigInt("18446744073709562301")
priceFeed.methods.getRoundData(validId).call()
.then((historicalRoundData) => {
document.getElementById('get-price-field').value = historicalRoundData.answer
})
web3 = Web3(Web3.HTTPProvider('https://kovan.infura.io/v3/<infura_project_id>'))
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"}]'
addr = '0x9326BFA02ADD2366b30bacB125260Af641031331'
contract = web3.eth.contract(address=addr, abi=abi)
# Valid roundId must be known. They are NOT incremental.
# invalidRoundId = 18446744073709562300
validRoundId = 18446744073709562301
historicalData = contract.functions.getRoundData(validRoundId).call()
print(historicalData)