DEV Community

Cover image for Retrieving latest asset prices in your React dApp
Makar Murashov
Makar Murashov

Posted on

Retrieving latest asset prices in your React dApp

What if you need to show in your app today's BTC/USD price? Or convert the user's balance from ETH to BTC? Or are you building an Elon Musk fan site and want to provide the latest Tesla (TSLA) stock price updates?
Today we will use Chainlink to retrieve the latest assets prices in a single call and provide it to your app components using React Context

Chainlink Data Feeds

The easiest and quickest way to do that is to use the data feed provided by Chainlink. We will use the ETH/USD pair as an example.

First, we need to find the contract's address for our pair to call to. There is a handy address's list for every pair supported by Chainlink.
In our case the address will be 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419.
Let's write a function to get the latest price. To do that we need to call a contract by its address and provide the ABI. We will use Ethers.js library to help us with it (you could also use Web3.js, it doesn't matter here).

// getLatestPrice.ts
import { providers, Contract, BigNumber } from 'ethers'

const provider = new providers.JsonRpcProvider("https://mainnet.infura.io/v3/<infura_project_id>")
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" }]
const ETH_USD_RATE_ADDRESS = '0x9326BFA02ADD2366b30bacB125260Af641031331'
const priceFeed = new Contract(ETH_USD_RATE_ADDRESS, aggregatorV3InterfaceABI, provider)

export function getLatestPrice(): Promise<BigNumber[]> {
  const priceFeed = new Contract(ETH_USD_RATE_ADDRESS, aggregatorV3InterfaceABI, provider)
  return priceFeed.latestRoundData()
}
Enter fullscreen mode Exit fullscreen mode

Asset price context

In order to use the latest price data inside our app we need to create a context that will periodically update the price and provide the value to our components.

// AssetPriceContext.ts
import { utils } from 'ethers'
import { createContext, useEffect, useRef, useState } from 'react'
import { getLatestPrice } from './getLatestPrice'

interface ContextProps {
  conversionDate: number | null;
  conversionRate: number | null;
}

const UPDATE_INTERVAL_TIMEOUT = 180000 // 3 minutes

export const DEFAULT_CONTEXT: ContextProps = {
  conversionDate: null,
  conversionRate: null,
}

export const AssetPriceContext = createContext<ContextProps>(DEFAULT_CONTEXT)

export const useAssetPrice = (): ContextProps => {
  const [state, setState] = useState<ContextState>(DEFAULT_CONTEXT)
  const updateInterval = useRef<ReturnType<typeof setTimeout>>()

  const updateAssetPrice= async () => {
    let conversionDate = null
    let conversionRate = null

    try {
      const roundData = await getLatestPrice()

      conversionDate = Number(roundData[3].toString()) * 1000
      conversionRate = Number(utils.formatUnits(roundData[1], 8))
    } catch (error) {
      console.log(error) 
    }

    setState({conversionDate, conversionRate })
  }

  const startUpdate = async () => {
    stopUpdate()

    await updateAssetPrice()

    updateInterval.current = setInterval(async () => {
      await updateAssetPrice()
    }, UPDATE_INTERVAL_TIMEOUT)
  }

  const stopUpdate = () => {
    if (updateInterval.current) {
      clearInterval(updateInterval.current)
    }
  }

  useEffect(() => {
    startUpdate()
    return stopUpdate
  }, [])

  return state
}
Enter fullscreen mode Exit fullscreen mode

Use in a component

Now we are ready to use the latest price, let's connect context to our app first:

// App.tsx
import { AssetPriceContext, useAssetPrice } from './AssetPriceContext'
import { EthBalance } from './EthBalance'

export default function App() {
  const assetPrice = useAssetPrice()

  return (
    <AssetPriceContext.Provider value={assetPrice}>
      <div>
        <h1>Chainlink Data Feeds example</h1>
        <EthBalance />
      </div>
    </AssetPriceContext.Provider>
  );
}
Enter fullscreen mode Exit fullscreen mode

And create a simple component to convert our balance from ETH to USD:

// EthBalance.tsx
import React, { useContext } from 'react'
import { AssetPriceContext } from './AssetPriceContext'

const BALANCE_ETH = 1

export const EthBalance: React.FC = () => {
  const { conversionRate, conversionDate } = useContext(AssetPriceContext)

  const balanceUSD = conversionRate ? BALANCE_ETH * conversionRate : '...'
  const updatedAt = conversionDate
    ? new Intl.DateTimeFormat(undefined, { dateStyle: 'full', timeStyle: 'medium' }).format(new Date(conversionDate))
    : '...'

  return (
    <div>
      <p>
        My balance is {BALANCE_ETH} ETH / {balanceUSD} USD
      </p>
      <p>
        Updated at {updatedAt}
      </p>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Which will result something as follows:

My balance is 1 ETH / 1557 USD
Updated at Saturday, 11 June 2022 at 22:29:16
Enter fullscreen mode Exit fullscreen mode

Conclusion

Chainlink Data Feeds are relatively simple to use, yet it is a powerful instrument to connect your dApp to real-world data. More read is available here

Hope you enjoyed this tutorial, stay tuned for more.

Discussion (0)