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()
}
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
}
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>
);
}
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>
)
}
Which will result something as follows:
My balance is 1 ETH / 1557 USD
Updated at Saturday, 11 June 2022 at 22:29:16
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.
Top comments (0)