APIs used:
  • Get native token balance for address  
  • Get token balances for address  
  • Get NFTs for address  

As decentralized finance and Web3 applications are rapidly growing in popularity, there is also a rise in investors building their crypto asset portfolios across different blockchains. Keeping track of these assets as they spread across these protocols can be daunting, especially without using the right tools. This is where a Web3 portfolio tracker comes in handy.

In this guide, you’ll learn how to build your own Web3 portfolio tracker using the Covalent Unified API. You can check out and interact with the Portfolio Tracker application we will build here!

The Covalent Unified API

Covalent provides a unified API to easily access on-chain data across multiple blockchains like Ethereum, Binance Chain (BSC), Polygon and many more. We will be using the Covalent API to build a portfolio tracker that can provide a list of all the assets (ERC-20 tokens and NFTs) a wallet address is holding and also displays the market value of these assets which gives the users a clear overview of how much they have invested across multiple blockchains.

Prerequisites

  • A good understanding of JavaScript.

  • Knowledge of React.js.

  • Knowledge of how to build front-end applications using React.js.

  • Knowledge of how to interact with APIs.

  • Have Node.js and NPM or Yarn installed on your computer.

  • Have VSCode installed.

Get a Covalent API Key

Interacting with the Covalent Unified API requires that you have an account on the Covalent platform which gives you access to generating an API key for interacting with the API endpoints. Use the following guide to generate an API key:

  • Navigate to the Covalent Website at covalenthq.com.

  • Click on the Signup for a free API key button on the main page to create an account.

  • On the signup page, fill out the required input fields, check the Terms of Use checkbox, and click the Sign me up button.

  • After signing up, verify your email and log in to your Covalent account. You will be asked to provide some information about yourself in the input fields.

  • Once you have completed all the necessary onboarding/signup steps, go to covalenthq.com/platform to create your own API key. An API key will be created for you automatically, else, click the dropdown beside the Manage API keys button and click the Create button.

Now you have created an account on Covalent, and you also have an API key to interact with the Covalent unified API, let’s proceed to building the Portfolio tracker.

Building the Portfolio Tracker

Let’s start building the portfolio tracker application.

1

Creating a React Application

React.js will be used for the purpose of this application. We therefore need to scaffold a react project. Let’s get started!

  • Create a folder in your PC named portfolio_tracker.

  • Open a terminal on the portfolio_tracker folder and run:

    npx create-react-app .

  • This above command will create a react application inside the portfolio_tracker folder you have created.

Open the portfolio_tracker folder using VSCode. It should look like this:

2

Setting up Bootstrap in the React Application

Before we start coding the portfolio tracker logic, we need to set up the React application with a UI framework for design implementation.

For the purpose of this course, we will be using Bootstrap 4 (A CSS framework for building responsive web pages). Follow the following steps:

  • After opening the portfolio_tracker in your VSCode, navigate to public/index.html.

  • Replace everything in your index.html with the attached code.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Covalent Portfolio Tracker"
    />
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <title>Portfolio Tracker</title>

    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>

    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
  </body>
</html>

Your index.html file should look like this:

3

Installing the Covalent SDK

There are a couple of ways to interact with the Covalent Unified API. This can be done by directly calling the API HTTPS endpoints or by using the Covalent SDK. In this article, we will be using the Covalent SDK which is a seamless way of interacting with Covalent API.

To use the SDK, we need to first install it into the portfolio_tracker project.

  • Open up a VSCode terminal in your portfolio_tracker application.

  • Paste the command and press enter. This will install the Covalent SDK.

4

Replace the Code in App.js

  • Once you are done installing the SDK, head over to src/App.js.

  • Replace everything inside this file with the code below:

import React, { useState, Fragment } from "react";
import { CovalentClient } from "@covalenthq/client-sdk";
import LoadingSpinner from "./UI/LoadingSpinner";
import { ethers } from 'ethers';


function App() {
  const [address, setAddress] = useState("");
  const [network, setNetwork] = useState("eth-mainnet");
  const [tokenData, setTokenData] = useState(null);
  const [nftData, setNftData] = useState(null);
  const [nativeCurrencyData, setNativeCurrencyData] = useState(null);
  const [loading, setLoading] = useState(false);

  const client = new CovalentClient(process.env.REACT_APP_API_KEY);

  const getWalletTokenBalance = async () => {
    try {
      const resp = await client.BalanceService.getTokenBalancesForWalletAddress(network, address);
      setTokenData(resp.data);
    } catch (error) {
      console.error(error);
    }
  }

  const getWalletNfts = async () => {
    try {
      const resp = await client.NftService.getNftsForAddress(network, address);
      setNftData(resp.data);
    } catch (error) {
      console.error(error);
    }
  }

  const getNativeTokenBalance = async () => {
    try {
      const resp = await client.BalanceService.getNativeTokenBalance(network, address);
      setNativeCurrencyData(resp.data);
    } catch (error) {
      console.error(error);
    }
  }

  const addressChangeHandler = (e) => {
    e.preventDefault();
    setAddress(e.target.value);
  }

  const networkChangeHandler = (e) => {
    e.preventDefault();
    setNetwork(e.target.value);
  }

  const fetchWalletTokens = async (e) => {
    e.preventDefault();

    if(address === "" || network === "") {
      alert("input an address and select a blockchain network");
    } else {
      setLoading(true);

      await getNativeTokenBalance();
      await getWalletTokenBalance();
      await getWalletNfts();

      setLoading(false);
    }
  }

  return (
    <div className="App">
      <div className="container">
        <nav className="navbar navbar-expand-lg navbar-light bg-primary  mb-1">
          <a className="navbar-brand text-light font-weight-bold" href="!#">Portfolio Tracker</a>
          
          <button className="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation"
          >
              <span className="navbar-toggler-icon"></span>
          </button>

          <div className="collapse navbar-collapse" id="navbarText">
            <ul className="navbar-nav mr-auto"></ul>
            
            <span className="navbar-text text-light">
              Track your crypto assets across multiple chains
            </span>
          </div>
        </nav>

        <div className="row">
          <div className="col-md-12">
            <form>
              <div className="input-group mb-3 input-group-lg">
                <input type="text" className="form-control" placeholder="Wallet Address" onChange={addressChangeHandler} />

                <select className="form-control" value={network} onChange={networkChangeHandler}>
                  <option value="eth-mainnet">Ethereum</option>
                  <option value="bsc-mainnet">Binance</option>
                  <option value="matic-mainnet">Polygon</option>
                  <option value="fantom-mainnet">Fantom</option>
                </select>
                
                <div className="input-group-append">
                  <button className="btn btn-primary" onClick={fetchWalletTokens}>Explore</button>
                </div>
              </div>
            </form>
          </div>
        </div>

        {loading ? 
          <div className="text-center mt-5">
            <LoadingSpinner />
            <h3 className="text-center text-black mt-3">Loading...</h3>
          </div>
        : 
          <Fragment>
            <div className="row mt-4">
              <div className="col-md-4">
                <div className="card bg-primary text-white">
                  <div className="card-body">
                    <span>
                      Currency: {nativeCurrencyData !== null && nativeCurrencyData.items.length > 0 && ethers.utils.formatUnits(nativeCurrencyData.items[0].balance, nativeCurrencyData.items[0].contract_decimals)}
                      <strong>{nativeCurrencyData !== null && nativeCurrencyData.items.length > 0 && nativeCurrencyData.items[0].contract_ticker_symbol}</strong>
                    </span>
                    <h3>{nativeCurrencyData !== null && nativeCurrencyData.items.length > 0 && nativeCurrencyData.items[0].pretty_quote}</h3>
                  </div>
                </div>
              </div>

              <div className="col-md-4">
                <div className="card bg-primary text-white">
                  <div className="card-body">
                    <span>Tokens</span>
                    <h3>{tokenData !== null && tokenData.items.length}</h3>
                  </div>
                </div>
              </div>

              <div className="col-md-4">
                <div className="card bg-primary text-white">
                  <div className="card-body">
                    <span>NFTs:</span>
                    <h3>{nftData !== null && nftData.items.length}</h3>
                  </div>
                </div>
              </div>
            </div>

            <div className="row mt-5">
              <div className="col-md-12">
                <ul className="nav nav-tabs nav-justified">
                  <li className="nav-item">
                    <a className="nav-link active" data-toggle="tab" href="#tokens">Tokens</a>
                  </li>
                  <li className="nav-item">
                    <a className="nav-link" data-toggle="tab" href="#nfts">NFTs</a>
                  </li>
                </ul>

                <div className="tab-content">
                  <div className="tab-pane container active" id="tokens">
                    <div className="table-responsive my-4">
                      <table className="table table-striped table-light">
                        <thead>
                          <tr>
                            <th scope="col">SN</th>
                            <th scope="col">Token</th>
                            <th scope="col">Symbol</th>
                            <th scope="col">Amount</th>
                            <th scope="col">Value</th>
                          </tr>
                        </thead>

                        <tbody>
                          {tokenData !== null && tokenData.items.length > 0 && 
                            tokenData.items.map((item, index) => (
                              <tr key={item.contract_address}>
                                <th scope="row">{index + 1}</th>
                                <td><img style={{ width: "30px" }} src={`${item.logo_url}`} alt="" /> {item.contract_name}</td>
                                <td>{item.contract_ticker_symbol}</td>
                                <td>{ethers.utils.formatUnits(item.balance_24h, item.contract_decimal)}</td>
                                <td>{item.pretty_quote_24h === null ? "$0.00" : item.pretty_quote_24h}</td>
                              </tr>
                            ))
                          }
                        </tbody>
                      </table>
                    </div>
                  </div>
                  <div className="tab-pane container fade" id="nfts">
                  <div className="table-responsive my-4">
                      <table className="table table-striped table-light">
                        <thead>
                          <tr>
                            <th scope="col">SN</th>
                            <th scope="col">NFT</th>
                            <th scope="col">Symbol</th>
                            <th scope="col">Contract Address</th>
                            <th scope="col">Floor Price</th>
                          </tr>
                        </thead>

                        <tbody>
                          {nftData !== null && nftData.items.length > 0 && 
                            nftData.items.map((item, index) => (
                              <tr key={item.contract_address}>
                                <th scope="row">{index + 1}</th>
                                <td>{item.contract_name}</td>
                                <td>{item.contract_ticker_symbol}</td>
                                <td>{item.contract_address}</td>
                                <td>{item.pretty_floor_price_quote === null ? "$0.00" : item.pretty_floor_price_quote}</td>
                              </tr>
                            ))
                          }
                        </tbody>
                      </table>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </Fragment>
        }
      </div>
    </div>
  );
}

export default App;
5

Create Loading Components

We need some UI components to display when the app is loading, this is displayed when the app is making an API request call to the Covalent API, giving users a nice feel to know that something is running in the background.

  • Create a folder named UI inside the src folder.

  • Inside the UI folder, create two files named LoadingSpinner.js, and LoadingSpinner.module.css.

Inside the LoadingSpinner.js, paste the attached code.

import classes from './LoadingSpinner.module.css';

const LoadingSpinner = () => {
  return <div className={classes.spinner}></div>;
}

export default LoadingSpinner;

Inside the LoadingSpinner.module.css, paste the attached code.

.spinner {
    display: inline-block;
    width: 80px;
    height: 80px;
  }
  .spinner:after {
    content: ' ';
    display: block;
    width: 64px;
    height: 64px;
    margin: 8px;
    border-radius: 50%;
    border: 6px solid black;
    border-color: black transparent black transparent;
    animation: spinner 1.2s linear infinite;
  }
  @keyframes spinner {
    0% {
      transform: rotate(0deg);
    }
    100% {
      transform: rotate(360deg);
    }
  }
6

Create a .env.local File to Keep API Key

The .env file is used to keep some private data, example the API key.

  • In the root project directory, create a .env.local file.

  • Inside the .env.local file create a variable and set the value to your Covalent API key.

After creating and setting up the .env.local file, your application is now ready.

7

Run the React Application

In your VSCode terminal, run npm start. This will open the application on http://localhost:3000.

The portfolio application should look like this:

Interacting with the Portfolio Tracker

Let’s now interact with the portfolio tracker application we have created! To interact with the application:

  • Get any Ethereum wallet address and paste it inside the Wallet Address input field.

  • Select any blockchain you wish to see the portfolio and click the Explore button.

  • This will list out all the tokens (ERC-20 and NFTs) that the specified Ethereum address is holding.

The portfolio tracker shows all the ERC-20 tokens the given address is holding on Binance Smart Chain. In our example, it shows that the wallet address has thirty-one (31) ERC-20 tokens and one NFT on the Binance chain while also showing the value of these token assets.

The tracker also shows the NFT a given address is holding on Binance Smart Chain. You can switch chains and click the Explore button to check the wallet assets on other chains. This portfolio tracker supports four different blockchains; Ethereum, Binance, Polygon and Fantom.

Deploying the Portfolio Tracker

The final step of building the portfolio tracker is to deploy the application, making it accessible to every other person on the internet.

We will be deploying this application on Vercel using the following steps:

1

Add a New Project

  • Make sure that your application has been pushed to GitHub.

  • Go to your Vercel dashboard or create an account if you don’t already have an account.

  • On your Vercel dashboard, click on the Add New button and select Project.

2

Import

  • After clicking on Project, search for the project name, if you have not added the project to your Vercel, click on the Configure GitHub App button, add the project and save it.

  • When you have successfully configured the GitHub application, the project will now appear on your Vercel as shown. Click on the Import button.

3

Deploy

  • On clicking the Import button, you will be redirected to the deployment page. Set up the environment variables and click the Deploy button.

Your application will deploy successfully and you will be provided a link to access your own portfolio tracker application. Access the full codebase for the portfolio tracker here.

Conclusion

The portfolio tracker application is a utility application that comes in very handy in the Web3 and DeFi ecosystem, making it easy for investors to keep track of their investments across multiple chains. Of course, the application we have built can be improved, you can explore other options to make it better by adding support for more blockchains and also showing on-chain transactions of a wallet address. Cheers, and Happy Building!

References & Further Reading