How to Display Crypto and Fiat Prices on a Frontend Using JavaScript or Solidity

Web3 developers often opt to display a platform or protocol’s native cryptocurrency on the frontend rather than display a conversion to a fiat currency. But while it’s easy to focus on the native currency, displaying a fiat conversion might be a better option. Pricing in crypto assumes knowledge of a currency’s current value, which tends to fluctuate, and also creates a less welcoming experience for new users, many of whom feel more comfortable referencing USD or their native currency. 

Luckily, converting between currencies like ETH and USD is simple with Chainlink Data Feeds, meaning developers can provide a better experience to users with just a few easy steps. This technical tutorial breaks down how to display both crypto and fiat prices on a frontend using the ETH / USD Chainlink Price Feed.

What Are Chainlink Data Feeds?

Chainlink Data Feeds are a set of smart contracts that provide access to secure and reliable real-world data sources. They are powered by independent, highly reputable, and geographically distributed node operators, which helps to ensure the reliability of the data returned. The ETH / USD feed, for example, currently utilizes 31 separate oracles, or sources of information, to determine the current trusted answer for the price of ETH in USD.

Why do we need 31 sources for the same information? Using multiple sources to aggregate a validated response means that there can be no single point of failure and protects against data tampering.

A Note On Providers

When interacting with smart contracts, it is necessary to have a provider for the Web3 connection. Often, this will be available via the user’s wallet connection. If that isn’t available, or you don’t need to have the user’s wallet connected, you can accomplish the same functionality via

const provider = new ethers.providers.JsonRpcProvider('RPC_URL_HERE');

RPC_URL_HERE can be obtained from a node provider such as Alchemy, Infura, Moralis, or QuickNode. The provider is our ”gateway” to the blockchain.

The other requirement for this tutorial is an Ethereum JavaScript Library. In this example, I’m using ethers. You will need to have it installed for the example to work.

npm install --save ethers

Deploying an ETH / USD Conversion Using JavaScript

Now for the code that you’ll need to display the ETH / USD Price Feed on the frontend. 

If you would like to follow along, the code for this example can be found in the examples repo. Follow the instructions in the README.md in order to run this sample locally. This example uses Svelte, but the same concepts should apply to any frontend JavaScript framework, such as React or Vue. There are also several other starter kits you could use within the SmartContract GitHub repo.

This code can be used on the frontend via an import statement

import { getETHPrice } from '../utils/getETHPrice';

Then, store the results as the value of ETH in USD

value = await getETHPrice();

Use this value to go from ETH to USD, assuming ethAmount is the amount of ETH to convert. 

usdValue = Number(ethAmount * value).toFixed(2);

Here is the complete file:

~/utils/getEthPrice.js

import { ethers } from 'ethers';


export async function getETHPrice() {
    const provider = new 
ethers.providers.JsonRpcProvider('RPC_URL_HERE');

    // This constant describes the ABI interface of the contract, which will provide the price of ETH
    // It looks like a lot, and it is, but this information is generated when we compile the contract
    // We need to let ethers know how to interact with this contract.
    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'
        }
    ];
    // The address of the contract which will provide the price of ETH
    const addr = '0x8A753747A1Fa494EC906cE90E9f37563A8AF630e';
    // We create an instance of the contract which we can interact with
    const priceFeed = new ethers.Contract(addr, aggregatorV3InterfaceABI, provider);
    // We get the data from the last round of the contract 
    let roundData = await priceFeed.latestRoundData();
    // Determine how many decimals the price feed has (10**decimals)
    let decimals = await priceFeed.decimals();
    // We convert the price to a number and return it
    return Number((roundData.answer.toString() / Math.pow(10, decimals)).toFixed(2));
}

The aggregatorV3InterfaceABI or ABI makes up much of the code we need. This is the data structure for the contract we will be interacting with, which we need to let ethers know about. Typically, you might store these in separate JSON files within a frontend project. In this case, it’s included in the util file to keep everything together.

// The address of the contract which will provide the price of ETH
    const addr = '0x8A753747A1Fa494EC906cE90E9f37563A8AF630e';

This contract address will change depending on the network or the price pair; you can use any of these Data Feed Contract Addresses.

// We get the data from the last round of the contract 
    let roundData = await priceFeed.latestRoundData();
    // Determine how many decimals the price feed has (10**decimals)
    let decimals = await priceFeed.decimals();
    // We convert the price to a number and return it
    return Number((roundData.answer.toString() / Math.pow(10, decimals)).toFixed(2));

Interacting with the contract is straightforward. We get the latestRoundData, which returns:

  • roundId: The round ID.
  • answer: The price.
  • startedAt: Timestamp of when the round started.
  • updatedAt: Timestamp of when the round was updated.
  • answeredInRound: The round ID of the round in which the answer was computed. We are interested in answer. This will be the price of ETH with one minor warning. We need to know the number of decimal places to include in the response. That’s where decimals comes in. It returns the number of decimal places to include.

We use the answer and decimal to return the value of one ETH:

return Number((roundData.answer.toString() / Math.pow(10, decimals)).toFixed(2));

Putting the Value To Use

Once we have the value of one ETH, we can use it to convert ETH to USD with ease. In the following example, rawETH is a string returned from the balance of a contract.

<!-- Display the raw ETH -->
{Number(rawETH).toFixed(8)} ETH
<!-- Display the ETH converted to USD -->
$ {(Number(rawETH) * value).toFixed(2)} USD

Chainlink Data Feeds provide a reliable solution for converting our value from ETH to USD using a secure method.

Using Solidity

So far, creating a utility function in a frontend application seems straightforward. But, what if we could eliminate the need for frontend developers to worry about pricing and handle it for them?

With a few changes to your contract, you can provide current price data to the end-user—all they need to worry about is connecting to your contract. This simplifies the frontend work needed. Let’s take a look at an example contract.

pragma solidity ^0.8.0;
// Import the chainlink Aggregator Interface
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";

// Create the contract
contract YourAwesomeContract { 
// Declare the priceFeed as an Aggregator Interface
    AggregatorV3Interface internal priceFeed;

    constructor() {
/** Define the priceFeed
* Network: Rinkeby
    * Aggregator: ETH/USD
      * Address: 0x8A753747A1Fa494EC906cE90E9f37563A8AF630e
      */
        priceFeed = AggregatorV3Interface(
            0x8A753747A1Fa494EC906cE90E9f37563A8AF630e
        );
    } 

// OTHER CONTRACT LOGIC HERE

    /**
    * Returns the latest price and # of decimals to use
    */
    function getLatestPrice() public view returns (int256, uint8) {
// Unused returned values are left out, hence lots of ","s
        (, int256 price, , , ) = priceFeed.latestRoundData();
        uint8 decimals = priceFeed.decimals();
        return (price, decimals);
    }
}

First, we import the same Aggregator Interface that we used in the frontend version above.

import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";

We then need to let the aggregator know the address for the price feed contract we are interested in.

priceFeed = AggregatorV3Interface(
            0x8A753747A1Fa494EC906cE90E9f37563A8AF630e
        );

In our getLatestPrice() function, we call priceFeed.latestRoundData(). priceFeed is referencing the same contract we used in the getETHPrice util above. It returns:

  • roundId: The round ID.
  • answer: The price.
  • startedAt: Timestamp of when the round started.
  • updatedAt: Timestamp of when the round was updated.
  • answeredInRound: The round ID of the round in which the answer was computed.

The code here may look a little strange. The only value we are interested in is answer. Therefore, we do not assign the other values to variables. Leaving them off prevents the storage of unused data.

(, int256 price, , , ) = priceFeed.latestRoundData();

Changes to the Frontend

Now that the value is included in your smart contract, we just need to access the value from the contract on the frontend.

    async function getETHPrice() {
        let [ethPrice, decimals] = await 
yourAwesomeContract.getLatestPrice();
        ethPrice = Number(ethPrice / Math.pow(10, 
decimals)).toFixed(2);
        return ethPrice;
    }

This will create a much simpler interface for consumers of our contract, as they won’t need to know about oracles or import a separate ABI.

Summary

In this technical tutorial, we’ve demonstrated how easy it is to integrate a Chainlink Data Feed into your dApp, enabling users to easily reference a USD / ETH price conversion. There are a huge range of Chainlink Price Feeds for both cryptocurrencies and fiat currencies available, offering developers the opportunity to adapt these steps using their pair of choice. 

Learn more about Chainlink by visiting chain.link or reading the documentation at docs.chain.link. To discuss an integration, reach out to an expert.

Need Integration Support?
Talk to an expert
Faucets
Get testnet tokens
Read the Docs
Technical documentation