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 more about how price 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 (please note that the increase may not be monotonic). Knowing the roundId of a previous round allows contracts to consume historical price data.
Important
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.
// 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)