How to get the price of Ethereum or any token in your smart contract Solidity
In this tutorial, we are going to learn how to get the price of Ethereum at any time in your smart contract in Solidity using the ChainLink oracle and its price feeds.
In this tutorial, we are going to learn how to get the price of Ethereum or any other crypto currency at any time in your smart contract in Solidity using the ChainLink oracle and its price feeds.
Here is an example that shows how to get the current price of Ethereum in USD and a historical price:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
contract TokenPriceFeedsConsumer {
AggregatorV3Interface internal priceFeed;
// The aggregator of the ETH/USD pair on the Goerli testnet
address priceAggregatorAddress = 0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e;
constructor() {
priceFeed = AggregatorV3Interface(priceAggregatorAddress);
}
// Get the latest ETH price
function getLatestEthereumPrice() external view returns (int256) {
(, int256 answer, , , ) = priceFeed.latestRoundData();
return answer;
}
// Get the historical price using a previous roundID
function getHistoricalEthereumPrice(uint80 roundID)
external
view
returns (int256, uint256)
{
(, int256 answer, , uint256 timeStamp, ) = priceFeed.getRoundData(
roundID
);
return (answer, timeStamp);
}
// Get the number of decimals used in the price data
function getDecimals() external view returns (uint8) {
return priceFeed.decimals();
}
}
How ChainLink Price Feeds work
ChainLink Price Feeds are a set of aggregator smart contracts that will each allow you to get the current or historical price of a cryptocurrency pair.
Some popular pairs are:
- Bitcoin and the
BTC/USD
pair - Ethereum and the
ETH/USD
pair - Binance Coin and the
BNB/USD
pair
They all use the same pattern so it's easy to interact with any of them without changing your code. You can get the price of Ethereum or the Bitcoin using the exact same code. The only difference is going to be the address of the smart contract you interact with.
If you don't know how to interact with an external smart contract in Solidity, basically you need to create an interface of the smart contract you want to interact with and then call that interface like a function and pass the address of contract to it.
You can then store the result of that call in a variable and use that variable to call the functions of the smart contract you want to interact with.
Check out our tutorial on how to interact with an external smart contract in Solidity if you want to learn more about this topic.
That's exactly what we do with ChainLink Aggregator contracts but we don't need to define the interface ourselves, we can just install the ChainLink contracts and import and use the Aggregator interface:
- First, install the ChainLink contracts using this command:
// using NPM:
npm install @chainlink/contracts
// or using Yarn:
yarn add @chainlink/contracts
- Then, import the Aggregator Interface and use it:
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
contract PriceFeedConsumer {
address aggregatorAddress = 0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e;
constructor() {
aggregator = AggregatorV3Interface(aggregatorAddress);
}
}
- Now, you can just call any function that's in the aggregator contract. Here is an example in which I call the
decimals
function:
function getDecimals() external view returns (uint8) {
return aggregator.decimals();
}
In the future, the interface might change. But the current aggregator we use at the time of writing this article (late 2022), implements the AggregatorV3Interface
and has the following functions:
decimals
: The number of decimals of precision that the price hasdescription
: The description of the aggregatorgetRoundData
: Returns the historical price of the currency the aggregator returns. More on that in the last section of this article.latestRoundData
: Returns the most recent price of the currency the aggregator returns. More on that in the next section of this article.version
: The version of the aggregator
More information about price feeds here:
.png)
And refer to this page to see all the tokens you can get the price of and their corresponding aggregator contract addresses:
.png)
On this page, if you check the "Show more details" checkbox, it will also show you the decimals used in the price returned.
How to get the current price of any token
We use the latestRoundData
function to get the price from the latest round. In other words, it allows you to get the most recent price of Ethereum or any token you want depending on the aggregator you interact with.
Here is an example of function that reads the current price of Ethereum (it interacts with the Goerli testnet):
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
contract EthereumPriceConsumer {
AggregatorV3Interface internal ethPriceFeed;
address ethAggregatorAddress = 0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e;
constructor() {
ethPriceFeed = AggregatorV3Interface(ethAggregatorAddress);
}
function getLatestEthereumPrice() external view returns (int256) {
(, int256 price, , , ) = ethPriceFeed.latestRoundData();
return price;
}
}
The latestRoundData
function takes no parameter and returns the following values in that same order:
roundId
: Auint80
representing the identifier of the round. A round is a period of time in which the price was updated.answer
: Aint256
which is the price of the currency you queriedstartedAt
: Auint256
timestamp of when the round started.updatedAt
: Auint256
timestamp of when the round was updated.answeredInRound
: Auint80
representing the ID of the round the answer was updated in. IfansweredInRound
is equal toroundId
, then the answer is fresh. Otherwise, if it's less than theroundId
, the price was carried over from some time ago.
How to get historical prices of any token
To get the historical price of a currency, we use the getRoundData
function which takes in parameter the round ID that you want to get the data for.
It returns the same data format as the latestRoundData
function but the data returned corresponds to the round passed in the parameters:
function getHistoricalEthereumPrice()
external
view
returns (int256, uint256)
{
uint80 roundID = 92233720368547771158;
(, int256 answer, , uint256 timeStamp, ) = priceFeed.getRoundData(
roundID
);
return (answer, timeStamp);
}
Unfortunately, you can't query historical data for a specific timestamp which would be the ideal way to get historical data.
So, you need to have the round ID of the round that was computed at the date and time you want.
For that, you need to know how the round IDs are computed. They are made of:
- A phase ID which starts at 1 and is incremented at every round by the aggregator
- The aggregator round ID which also starts at 1 and is incremented at every round
Then, the round ID is computed using this formula:
roundId = uint80((uint256(phaseId) << 64) | aggregatorRoundId);
So, we left shift the phase ID by 64 bits and then concatenate it with the aggregator round ID.
To do the opposite and get the phase ID and the aggregator ID out of the round ID, you can right shift the round ID which is equivalent to dividing it by 2^64. That will give you the phase ID.
And to get the aggregator round ID, you can do: aggregatorRoundId = uint64(roundId)
.
Then you can work your way backwards and decrement the aggregator round ID and the phase ID, compute an old round ID and fetch the data for that round.
It is strongly recommended to do that off-chain to avoid using too much gas.
If you want to get more information about round IDs and how to compute old round IDs, check out the ChainLink documentation here:
.png)
And that's it 🎉
Thank you for reading this article