I want to read all (!) historical price feed prices of a specific Chainlink price feed proxyContract off-chain via Javascript.

Based on the docs article I iterate my query, each time reducing my roundId. I understand there are gaps in the roundId sequences so I ignore no returns and continue my iteration. I have also found the following amazing video to do a multi-call, but I am missing the following components to complete my search:

  1. What if the underlying aggregatorContract was updated/replaced? I understand there is something like a phaseId (in addition to roundId?) but can't find directions for how to use that in the docs.
  2. How can I identify that I have reached the end of the historical price data (across the history of aggregatorContracts for that specific price feed)? Is there a specific way to identify this (error code, phaseId=0 && roundId=0 ...)?


3 Answers 3


Each proxy contract can have multiple underlying aggregator contracts, with one of them being set as the 'active' one at any given instance. Ie whenever the Chainlink Labs team deploys new versions of the aggregator, they update the proxy contract to set the current aggregator version to be the new one, and all new price data starts getting written to the new aggregator.

Based on this, if you want ALL historical price data for a proxy contract/price pair, you'd need to go through all aggregators and get all the price data in each one, then combine it all at the end. Eg let's say the ETH/USD proxy used aggregator with phase ID of 3 from July to now, then they used aggregator with phase ID 2 from jan to july, then they used aggregator with phase ID 1 from nov 2020 - jan 2020. You would grab all the pricing data from each aggregator, then combine the results.

To find the current aggregator contract address for a proxy contract, you can call the aggregator getter function in the proxy contract

The phase ID can be thought of as an incrementing ID number that the proxy contract uses to identify each aggregator as it gets added to the proxy, so the first one is 1, second is 2 etc. To find out what the current phase ID is of the current aggregator, you can use the phaseId getter function in the proxy contract.

If the current one is phase ID 4, that means that there are 3 other aggregators that were used in the price feed at some stage. You can get the address of these other aggregators by passing in a phase ID into the proxy contract phaseAggregators getter function. Ie if the current phaseID is 4, you can pass in the parameters 3, 2, and then 1 to get the address of the previous aggregator contracts.

Once you have all the aggregator contract addresses, it's simply a matter of going through each one and getting the pricing data using the getRoundData function. However take note there's a difference between proxy contract round ID and aggregator contract round ID.

Aggregator round IDs start from 1 and increase by 1 each time. Eg you can use the aggregator latestRound getter function to find the last round that it stored, and then start from that number as an input parameter to getRoundData, and go back down to 1

Proxy round IDs are those big really long numbers like 36893488147419113293, and are actually just a derived value based on the aggregator phase ID and the aggregator round ID. This is done to ensure the proxy round IDs are always increasing in value, and that there will never be any overlap between the same rounds across different aggregators, eg round 5000 from aggregator with phase ID 1 should have a lower proxy round ID that round 5000 from aggregator with phase ID 2

In JS you can easily replicate the proxy round ID derived value with the following formula, passing in the phase ID of the aggregator and the aggregator round ID

(BigInt(_aggregatorPhaseId) << BigInt(64) | BigInt(_aggregatorRoundId)).toString()

You can even then take that derived value and pass it into the proxy contract getRoundData function, and get the same data result as calling getRoundData in the aggregator contract for the given aggregator round ID.

I find A good way to play around with these is to interact with the functions directly on etherescan, and see the data returned. Ie here's the Kovan ETH/USD proxy and its latest aggregator

One thing to note is that there is a chance that multiple aggregators could be getting updated at the same time as well (for fail-safe scenarios, when new aggregators get rolled out etc), so in your code that grabs all the data, you may need to handle the scenario where multiple aggregators have stored price data for the same time frame, and remove duplicates.

Regarding the answer your second question, the best way is to start from 'now', ie get the latest aggregator, grab the latest round in the aggregator and then go back to round ID 1 getting all pricing data, then get the previous aggregator and repeat, and keep going back in aggregators (phase ID = phase ID - 1) until you get all the data from the aggregator with phase ID of 1. Then you have all the data

Finally, not all aggregators are the same, some may have slightly different function implementations depending on what you're doing, so be aware of that. Each aggregator has a version getter function that will tell you what version of the aggregator contract it is. Here's an implementation of some javascript that gets historical price data at a particular point in time (as opposed to all data), you may find some code snippets useful. This one looks at the ethereum logs to get round IDs though, because it's looking for data at a particular point in time as opposed to all historical data

  • Very clear and complete answer, thanks @harry!
    – r035oewm
    Commented Dec 4, 2021 at 6:22

As mentioned above Chainlink may have more than one aggregator producing price data at a given time. My follow-up question is: How can we determine which of the parallel aggregator data was the 'official' feed data at the time?

Example USDC / USD (mainnet 0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6).

  • The aggregator with phase ID 1 delivered data from aggregator round ID 1 up to aggregator round id 295 (function latestRound on contract 0x3B15a92872435C01c27201AAe0968839fB45217D).
  • The aggregator with phase ID 2 started to produce data way earlier (function getRoundData(1) on contract 0x789190466E21a8b78b8027866CBBDc151542A26C) than the last available data from aggregator 1.

For this specific example: How can we find out when the main feed contract (0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6) switches from data from the aggregator 1 to aggregator 2?


About the question 1

aggregatorContract has versions is his own name, now it is V3:


  • As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.
    – Community Bot
    Commented Dec 3, 2021 at 19:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.