Javascript: How to Read All Historical Price Data of a Chainlink Price Feed

chainlinkjavascriptprice

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 …)?

Thanks!

Best Answer

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

Related Topic