- Get historical portfolio value over time
Are you a successful investor, or a degen with a large crypto bag? Have you ever wondered how you can track the historic value of your crypto holdings for the last 30/60/90 days or longer, to see which tokens have gone up, what has gone down, and the rate of change of each tokens? You’ve come to the right place.
In this how-to guide, you’ll be learning how to get the historic portfolio value data of all the tokens of a wallet or smart contract address, for up to 100+ chains, using the Covalent API. This includes chains like Ethereum, Polygon, Avalanche, BSC, Arbitrum, Oasis, Optimism, and many more.
By the end of this guide, you’ll be able to build a historic holdings component that looks like this:
Let’s get started!
(Estimated time to follow along: 15mins)
Prerequisite
Basic familiarity with React
Some HTML/CSS knowledge
Fetching data using APIs
Step 1: Initialize a React project
npx create-react-app portfolio-chart
Step 2: Install the required libraries
npm i recharts
We’ll be using Recharts, a beautiful charting library for our purpose.
Step 3: Fetch the data
The endpoint we’ll be using is Covalent’s Get historic portfolio value over time endpoint. Let’s define it within App.js, along with the chain name and a sample wallet address to use:
const chainName = 'eth-mainnet' const walletAddress = '0x6564466f510c8311FDF935C5B2566201AAdFceA3' // a sample wallet address const apiKey = process.env.REACT_APP_COVALENT_API_KEY const historicPortfolioValueEndpoint = `https://api.covalenthq.com/v1/${chainName}/address/${walletAddress}/portfolio_v2/`
Be sure to create a .env
file at your project root and store your API key within it, like so:
REACT_APP_COVALENT_API_KEY='ckey_xx8c4xxxxxxxxxxxxx5008'
If you don’t have a Covalent API key, you can get it for free here.
Within your App.js, fetch the data using an effect hook:
function App() { const [data, setData] = useState(null); const [keys, setKeys] = useState(null); useEffect(() => { fetch(historicPortfolioValueEndpoint, {method: 'GET', headers: { "Authorization": `Basic ${btoa(apiKey + ':')}` }}) .then(res => res.json()) .then(res => { const rawData = res.data.items console.log(rawData) }) }, [])
Run npm start
, and head over to your dev console.
You should be able to see this:
Congratulations, you’ve successfully retrieved the data!
Step 4: Examining the response
Before we pipe the data into our chart, let’s take a look at what the response looks like:
// Response for the Get historic portfolio value over time endpoint [ { "contract_decimals": 18, "contract_name": "Ether", "contract_ticker_symbol": "ETH", "contract_address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", "supports_erc": null, "logo_url": "<https://logos.covalenthq.com/tokens/1/0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee.png>", "holdings": [...] }, { "contract_decimals": 18, "contract_name": "Dai Stablecoin", "contract_ticker_symbol": "DAI", "contract_address": "0x6b175474e89094c44da98b954eedeac495271d0f", "supports_erc": null, "logo_url": "<https://logos.covalenthq.com/tokens/1/0x6b175474e89094c44da98b954eedeac495271d0f.png>", "holdings": [...] }, { "contract_decimals": 18, "contract_name": "Fantom Token", "contract_ticker_symbol": "FTM", "contract_address": "0x4e15361fd6b4bb609fa63c81a2be19d873717870", "supports_erc": null, "logo_url": "<https://logos.covalenthq.com/tokens/1/0x4e15361fd6b4bb609fa63c81a2be19d873717870.png>", "holdings": [...] } ...// more items below ]
Essentially, we have an array of objects. Each object represents a particular token holding, with base-level attributes such as contract_ticker_symbol
and its logo_url
.
The time series data is contained within the holdings
attribute. Here is what the array looks like when we expand it:
"holdings": [ { "timestamp": "2023-04-13T00:00:00Z", "quote_rate": 1965.6401, "open": { "balance": "12980227189076782647", "quote": 25514.457 }, "high": { "balance": "12980227189076782647", "quote": 25514.457 }, "low": { "balance": "12980227189076782647", "quote": 25514.457 }, "close": { "balance": "12980227189076782647", "quote": 25514.457 } }, { "timestamp": "2023-04-12T00:00:00Z", "quote_rate": 1919.6385, "open": { "balance": "12980227189076782647", "quote": 24917.346 }, "high": { "balance": "12980227189076782647", "quote": 24917.346 }, "low": { "balance": "12980227189076782647", "quote": 24917.346 }, "close": { "balance": "12980227189076782647", "quote": 24917.346 } } ... ]
This is a classic time-series array of OHLC quotes. Each holdings
array contains 30 items by default, which can be changed with the days
query parameter.
Step 5: Transform the data
Before we feed this data into our chart, we need to see what data structure Rechart’s line chart expects.
Heading over to the Recharts documentation of a line chart, we see that it requires the data object to be an array of objects of single-layer nesting.
const data = [ { name: 'Page A', uv: 4000, pv: 2400, amt: 2400, }, { name: 'Page B', uv: 3000, pv: 1398, amt: 2210, } ...
The component rendered, <LineChart />
, takes this data array as a prop.
return ( <LineChart width={500} height={300} data={data} margin={{ top: 5, right: 30, left: 20, bottom: 5, }} > ... </LineChart> )
We also see that the <Line />
component takes the attribute name to render the Y-values.
<Line type="monotone" dataKey="pv" stroke="#8884d8" activeDot={{ r: 8 }} />
This means that we’ll need to transform our data response to something of this shape:
[ { timestamp: "2023-04-13T00:00:00Z", ETH: 25514.457, USDC: 1330, UNI: 90, ... }, { timestamp: "2023-04-14T00:00:00Z", ETH: 25333.90, USDC: 1220, UNI: 10, ... }, ]
where each object in the array has a timestamp attribute that corresponds to an individual day, and all the tokens balances for that day are condensed into that object.
To do that, let’s write the following function that takes our raw Covalent API response and returns it in our desired format:
const transformForRecharts = (rawData) => { const transformedData = rawData.reduce((acc, curr) => { const singleTokenTimeSeries = curr.holdings.map(holdingsItem => { return { timestamp: holdingsItem.timestamp, [curr.contract_ticker_symbol]: holdingsItem.close.quote } }) const newArr = singleTokenTimeSeries.map((item, i) => Object.assign(item, acc[i])) return newArr }, []) return transformedData }
Applying the function to our response in the effect hook:
useEffect(() => { fetch(historicPortfolioValueEndpoint, {method: 'GET', headers: { "Authorization": `Basic ${btoa(apiKey + ':')}` }}) .then(res => res.json()) .then(res => { const rawData = res.data.items const transformedData = transformForRecharts(rawData) console.log(transformedData) setData(transformedData) }) }, [])
and console logging, we get this:
Perfect.
Time to pipe it into the chart!
Step 6: Render the chart
Create a LineChart component as per the instructions in the Recharts documentation.
return ( <LineChart width={800} height={500} data={data} margin={{ top: 5, right: 30, left: 20, bottom: 5, }} > <CartesianGrid strokeDasharray="3 3" /> <XAxis dataKey="timestamp" /> <YAxis /> <Tooltip /> <Legend /> {keys.map((item, i) => { return ( <Line dataKey={item} type="monotone" stroke={colors[i]}/> ) })} </LineChart> )
Your entire App.js component will now look like this:
import React, { useState, useEffect } from 'react'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend } from 'recharts'; const apiKey = process.env.REACT_APP_COVALENT_API_KEY const chainName = 'eth-mainnet' const walletAddress = '0x6564466f510c8311FDF935C5B2566201AAdFceA3' const historicPortfolioValueEndpoint = `https://api.covalenthq.com/v1/${chainName}/address/${walletAddress}/portfolio_v2/` const colors = ["#F44336", "#673AB7", "#03A9F4", "#4CAF50", "#FFEB3B", "#FF5722", "#607D8B", "#E91E63", "#3F51B5", "#00BCD4", "#8BC34A", "#FFC107", "#795548", "#9C27B0", "#2196F3", "#009688", "#CDDC39", "#FF9800", "#9E9E9E", "#EF9A9A", "#B39DDB", "#81D4FA", "#A5D6A7", "#FFF59D", "#FFAB91", "#B0BEC5", "#F48FB1", "#9FA8DA", "#80DEEA", "#C5E1A5", "#FFE082", "#BCAAA4", "#CE93D8", "#90CAF9", "#80CBC4", "#E6EE9C", "#FFCC80", "#EEEEEE", "#B71C1C", "#311B92", "#01579B", "#1B5E20", "#F57F17", "#BF360C", "#263238", "#880E4F", "#1A237E", "#006064", "#33691E", "#FF6F00", "#3E2723", "#4A148C", "#0D47A1", "#004D40", "#827717", "#E65100", "#212121"] function App() { const [data, setData] = useState(null); const [keys, setKeys] = useState(null); useEffect(() => { fetch(historicPortfolioValueEndpoint, {method: 'GET', headers: { "Authorization": `Basic ${btoa(apiKey + ':')}` }}) .then(res => res.json()) .then(res => { const rawData = res.data.items const transformedData = transformForRecharts(rawData) const dataKeys = rawData.map(item => item.contract_ticker_symbol) setKeys(dataKeys) setData(transformedData) }) }, []) if (!data) { return <div>Loading...</div>; } return ( <LineChart width={800} height={500} data={data} margin={{ top: 5, right: 30, left: 20, bottom: 5, }} > <CartesianGrid strokeDasharray="3 3" /> <XAxis dataKey="timestamp" /> <YAxis /> <Tooltip /> <Legend /> {keys.map((item, i) => { return ( <Line dataKey={item} type="monotone" stroke={colors[i]}/> ) })} </LineChart> ) }; export default App; const transformForRecharts = (rawData) => { const transformedData = rawData.reduce((acc, curr) => { const singleTokenTimeSeries = curr.holdings.map(holdingsItem => { // Formatting the date string just a little... const dateStr = holdingsItem.timestamp.slice(0,10) const date = new Date(dateStr) const options = { day: "numeric", month: "short" }; const formattedDate = date.toLocaleDateString("en-US", options); return { timestamp: formattedDate, [curr.contract_ticker_symbol]: holdingsItem.close.quote } }) const newArr = singleTokenTimeSeries.map((item, i) => Object.assign(item, acc[i])) return newArr }, []) return transformedData }
Running npm start
, you’ll be able to see the following:
All in a day’s work
Congratulations. You’ve successfully built a historic portfolio chart that can be used for all kinds of use cases.
While the immediate use case is to be able to track all your token holdings, you can also use this endpoint to:
Get the historic holdings value of a liquidity pool
Getting historic holdings of a DAO treasury
Basically, if you need to check the holdings of any on-chain address or smart contract, this endpoint will do the trick. The best part? Simply replace the chainName
with any of the other supported values to get an address’ holdings in other chains.
And there you have it, 100+ chains cross-chain historic token portfolio chart at your fingertips!
Liked this how-to guide? Give it a RT on Twitter.
Not fulfilling what you need? Reach out to us and let us know how to improve this endpoint.