Web3 developers often choose to display a platform or protocol’s native cryptocurrency on the frontend without converting it to a fiat equivalent. While this keeps users focused on the native token, showing fiat conversions can often be a better choice. Pricing crypto tokens requires an understanding of their current value, which is often volatile. This can also create a less welcoming experience for new users, many of whom feel more comfortable with references to the US dollar or their local currency.
Fortunately, converting between currencies like ETH and USD is straightforward using reliable data feeds, meaning developers can provide a better user experience with just a few simple steps. This technical guide explains how to display both cryptocurrency and fiat prices on the frontend using a trusted price feed source.
What Are Decentralized Data Feeds?
Decentralized data feeds are a set of smart contracts that provide secure and reliable real-world data sources. They are supported by independent, reputable, and geographically distributed node operators, which helps ensure the reliability of the returned data.
For example, a widely used ETH/USD data feed relies on multiple independent oracles, or information sources, to determine a trustworthy current price for Ethereum in US dollars.
Why use multiple sources for the same information? Aggregating responses from several sources eliminates single points of failure and helps prevent data manipulation.
Understanding the Provider
When interacting with smart contracts, a Web3-connected provider is essential. Typically, this is available through a user's connected wallet. If not, or if you don’t require a user’s wallet connection, you can achieve the same functionality by setting up your own provider.
const provider = new ethers.providers.JsonRpcProvider('RPC_URL_HERE');The RPC_URL_HERE can be obtained from a node service provider. The provider acts as our "gateway" to the blockchain.
Another requirement for this tutorial is a JavaScript library for Ethereum. In this example, we use the ethers library. You’ll need to install it for the code examples to work.
npm install --save ethersImplementing ETH/USD Conversion with JavaScript
Here is the code needed to display the ETH/USD price on the frontend.
If you'd like to follow along, the code for this example is available in a public repository. Instructions in the README.md file explain how to run the example locally. This example uses a modern frontend framework, but the same concepts apply to any JavaScript framework like React or Vue.
The code can be imported into the frontend using an import statement:
import { getETHPrice } from '../utils/getETHPrice';Then, store the result as the ETH price in USD:
value = await getETHPrice();Assuming ethAmount is the amount of ETH to be converted, use this value to convert from ETH to USD.
usdValue = Number(ethAmount * value).toFixed(2);Here is the complete utility file:
// ~/utils/getEthPrice.js
import { ethers } from 'ethers';
export async function getETHPrice() {
const provider = new ethers.providers.JsonRpcProvider('RPC_URL_HERE');
// ABI interface for the contract providing the ETH price
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'
}
];
// Address of the contract providing the ETH price
const addr = '0x8A753747A1Fa494EC906cE90E9f37563A8AF630e';
// Create a contract instance to interact with
const priceFeed = new ethers.Contract(addr, aggregatorV3InterfaceABI, provider);
// Get data from the latest round
let roundData = await priceFeed.latestRoundData();
// Determine the number of decimals in the price feed
let decimals = await priceFeed.decimals();
// Convert the price to a number and return it
return Number((roundData.answer.toString() / Math.pow(10, decimals)).toFixed(2));
}Most of the code we need is the aggregatorV3InterfaceABI, or ABI. This is the data structure of the contract we will interact with, and we need to let ethers know about it. Often, you might store this in a separate JSON file in a frontend project. In this example, it is included in the utils file to keep everything together.
The contract address will vary depending on the network or price pair. You can use any from the available data feed contract addresses.
Interacting with the contract is straightforward. We call 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 in which the answer was computed.
We are interested in the answer. This will be the price of ETH, with one caveat: we need to know the number of decimals included in the answer. That’s what decimals() returns.
We use the answer and decimals to return the ETH price:
return Number((roundData.answer.toString() / Math.pow(10, decimals)).toFixed(2));Using the Price Data
Once we have the price of a single ETH, we can easily convert ETH amounts to USD prices. In the example below, rawETH is a string returned from a contract’s balance.
{Number(rawETH).toFixed(8)} ETH
$ {(Number(rawETH) * value).toFixed(2)} USDUsing decentralized data feeds offers a reliable solution for converting asset values from ETH to USD using a secure methodology. 👉 Explore more strategies for real-time data integration
Implementing with Solidity
So far, creating a utility function in a frontend application seems simple. But what if we could eliminate the frontend developer’s concern about price and handle it for them?
With a few modifications to your contract, you can provide current price data to end-users—all they need to worry about is connecting to your contract. This simplifies the required frontend work. Let’s look at a contract example.
pragma solidity ^0.8.0;
// Import the required interface
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
contract YourAwesomeContract {
// Declare the priceFeed variable
AggregatorV3Interface internal priceFeed;
constructor() {
// Define the price feed
// Network: Ethereum
// Aggregator: ETH/USD
priceFeed = AggregatorV3Interface(0x8A753747A1Fa494EC906cE90E9f37563A8AF630e);
}
// Other contract logic here
/**
* Returns the latest price and number of decimals
*/
function getLatestPrice() public view returns (int256, uint8) {
(, int256 price, , , ) = priceFeed.latestRoundData();
uint8 decimals = priceFeed.decimals();
return (price, decimals);
}
}First, we import the same aggregator interface used in the frontend version above.
Then, we need to inform the aggregator about the contract address of the price feed we are interested in.
In our getLatestPrice() function, we call priceFeed.latestRoundData(). The priceFeed refers to the same contract used in the frontend utility above. It returns the same set of values.
The code might look a bit unusual here. We are only interested in the answer field, so we do not assign the other values to variables. Skipping them prevents storing unused data.
Changes on the Frontend
Now that the value data is included in your smart contract, we only need to access this data from the frontend.
async function getETHPrice() {
let [ethPrice, decimals] = await yourAwesomeContract.getLatestPrice();
ethPrice = Number(ethPrice / Math.pow(10, decimals)).toFixed(2);
return ethPrice;
}This creates a simpler interface for consumers of your contract because they don’t need to understand oracles or import a separate ABI.
Frequently Asked Questions
Why should I display fiat values alongside crypto tokens?
Displaying fiat values makes your application more accessible to a broader audience, especially those who are less familiar with cryptocurrency. It provides real-world context and helps users quickly understand the value of their assets without manual conversion.
How often are the price feeds updated?
Price feeds are updated regularly whenever a significant deviation in price occurs or at heartbeat intervals, ensuring that the data reflects current market conditions. The exact frequency can depend on the specific feed and network conditions.
Can I use this method for tokens other than ETH?
Yes, the same methodology applies to a wide variety of cryptocurrencies. You would use the appropriate contract address for the desired token pair (e.g., BTC/USD, LINK/ETH) from the available data feed sources.
Is this data reliable during high network congestion?
Decentralized data feeds are designed by multiple independent nodes, which helps maintain reliability even during periods of network congestion. However, response times may vary, and it's good practice to handle potential delays in your application.
Do I always need a provider like Alchemy or Infura?
Yes, to interact with the blockchain and read data from smart contracts, you need a connection point. This can be via a user's wallet provider or a dedicated RPC endpoint from a service provider.
What are the potential costs of reading price data?
Reading data from a blockchain is usually free if you are only calling view or pure functions that don’t change the state. You only incur gas costs when you write data or perform state-changing operations.
Summary
In this technical guide, we demonstrated how to easily integrate decentralized data feeds into your DApp, enabling users to view USD/ETH price conversions effortlessly. With a wide range of available price feeds for various cryptocurrencies and fiat currencies, developers can adapt the steps above to suit their specific price data needs.
This approach not only enhances user experience but also leverages secure and decentralized infrastructure for accurate financial data. 👉 Get advanced methods for smart contract development