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();
    }
}

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 has
  • description: The description of the aggregator
  • getRoundData: 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:

Using Price Feeds | Chainlink Documentation
Chainlink is the most widely used oracle network for powering universally connected smart contracts, enabling any blockchain to access real-world data & APIs.

And refer to this page to see all the tokens you can get the price of and their corresponding aggregator contract addresses:

Price Feed Contract Addresses | Chainlink Documentation
Chainlink is the most widely used oracle network for powering universally connected smart contracts, enabling any blockchain to access real-world data & APIs.

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: A uint80 representing the identifier of the round. A round is a period of time in which the price was updated.
  • answer: A int256 which is the price of the currency you queried
  • startedAt: A uint256 timestamp of when the round started.
  • updatedAt: A uint256 timestamp of when the round was updated.
  • answeredInRound: A uint80 representing the ID of the round the answer was updated in. If answeredInRound is equal to roundId, then the answer is fresh. Otherwise, if it's less than the roundId, 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:

Historical Price Data | Chainlink Documentation
Chainlink is the most widely used oracle network for powering universally connected smart contracts, enabling any blockchain to access real-world data & APIs.

And that's it 🎉

Thank you for reading this article