Skip to main content

How to Build an ERC20 Token Balance App with QuickNode SDK

Created on
Updated on
Nov 26, 2024

16 min read

Overview

Creating an app to display ERC20 token balances can be straightforward or complex, depending on your approach. The traditional method involves using libraries like ethers.js to interact directly with Ethereum blockchain and manage smart contract ABIs, which is time-consuming and technically demanding. Luckily, the QuickNode SDK simplifies this process. It's a powerful toolkit that makes working with QuickNode infrastructure easy, requiring just one function to retrieve token balances.

This guide will illustrate how to build an ERC20 token balance app using the QuickNode SDK.

What You Will Need

What You Will Do

  • Learn about the traditional method of fetching ERC20 token data
  • Learn how to utilize the QuickNode SDK for fetching token balances on Ethereum
  • Build a React app to display ERC20 token balances using QuickNode SDK

The Traditional Way

Traditionally, to fetch balances for ERC20 tokens, you would need to interact directly with the blockchain using a library like ethers.js, requiring knowledge of smart contract ABIs and manual handling of multiple contract interactions. You can learn more about this method in our comprehensive guide.

The QuickNode SDK streamlines this process by providing a simple interface to interact with token balances and other blockchain data without the need for direct contract interactions.

What is QuickNode SDK?

QuickNode SDK is a tool that simplifies interacting with EVM blockchains like Ethereum, Polygon, and Arbitrum. It provides easy-to-use functions to fetch token metadata, balances, and transfer histories without manually interacting with the blockchain or smart contracts.

As of now, the SDK primarily revolves around its Core module.

Core component is responsible for enabling RPC capabilities with your endpoint, supporting interaction via standard and extended RPC operations. At the time this guide is written, the Core module supports 20 EVM-compatible chains. Supported chains and more information can be found on the overview page of Core.

Some functions of QuickNode SDK have pagination and filtering support, which can be useful while building a decentralized application. In this guide, we will use the pagination specification.

Setting Up the QuickNode SDK

To effectively utilize the Token and NFT API v2 capabilities within the QuickNode SDK, we'll need to set up a QuickNode endpoint with the specific bundle add-on enabled. This setup provides seamless access to various functionalities offered by QuickNode, allowing for a wide range of interactions with blockchain data. If you haven't already, start by creating a free QuickNode account here.

Once you have your account, you can enable the Token and NFT API v2 bundle on your QuickNode endpoint free of charge. Here's how to do it:


  1. Log in to your QuickNode account and navigate to your dashboard.
  2. Select or create a new Ethereum node.
  3. On the node's dashboard, look for the add-ons section and locate the Token and NFT API v2 bundle.
  4. Enable this free add-on for your selected node.

QuickNode Endpoint with Add-on

After enabling the add-on, your QuickNode endpoint is ready to interact with the blockchain using the enhanced functionalities of the Token and NFT API v2. Copy and save your endpoint, as you will need it in the next section.

ERC20 Token Balance App Development

Before starting coding, let's learn more about the primary components and functionalities of the app and see how the app will look when it is completed.

  • QuickNode SDK: Utilized to interact with blockchain data, fetching balances of ERC20 tokens for the specified wallet address on the selected blockchain.

  • SearchBar: A component that allows users to input a wallet address they want to query.

  • TokenList: A display component that lists the ERC20 tokens along with their balances retrieved for the given wallet address.

  • Pagination: Implemented to navigate through the list of tokens in a paginated manner, showing a limited number of tokens per page (default is set to 5 tokens per page).

When a user enters a wallet address and initiates a search, the app uses the QuickNode SDK to fetch the token balances, updating the token list with the results and providing pagination controls to navigate through the token data pages.

ERC20 Token Balance UI Overview

Now, we are ready to build! Let's start by setting up a new React project and installing the necessary dependencies.

Run the following code in your terminal. This will set up a new React application, navigate to the project's folder, and install the QuickNode SDK.

npx create-react-app token-balance-app
cd token-balance-app
npm install @quicknode/sdk

Components

There are two components that are needed to build this app: SearchBar and TokenList. Run the command to create these component files.

echo > src/SearchBar.js
echo > src/TokenList.js

SearchBar Component

The SearchBar component includes an input field for a wallet address and a search button. The SearchBar validates the entered address using the isAddress method of viem, a JavaScript/TypeScript library for interacting with EVM chains similar to libraries like ethers.js and web3.js, and enables the search button only if the address is valid. When the user types into the input field, the onInputChange function updates the state with the current value. The onSearch function is triggered when the search button is clicked.

Open the src/SearchBar.js file and add the code below. Feel free to check the comments for detailed information.

import React, { useState, useEffect } from 'react'
import { viem } from '@quicknode/sdk'

function SearchBar({ onSearch, inputValue, onInputChange }) {
const [notAddress, setNotAddress] = useState(false) // State to keep track if the input value is a valid address

// useEffect hook to validate the address everytime the inputValue changes
useEffect(() => {
// If inputValue is a valid address, set notAddress state to false, otherwise true
viem.isAddress(inputValue) ? setNotAddress(false) : setNotAddress(true)
}, [inputValue]) // Dependency array with inputValue, so this effect runs when inputValue changes

return (
<div>
<input
className="search-bar"
type="text"
placeholder="Enter wallet address" // Placeholder text for the input field
value={inputValue} // Controlled input with its value set to the inputValue state
onChange={e => onInputChange(e.target.value)} // Update the inputValue state on each keystroke
/>
<button onClick={onSearch} disabled={notAddress}>
{' '}
{/* // Button to initiate the search, disabled if notAddress is true */}
{inputValue && notAddress ? 'Invalid Address' : 'Search'}
</button>
</div>
)
}

export default SearchBar

TokenList Component

The TokenList component receives a wallet address and an array of tokens as props and displays a list of ERC20 token details associated with that wallet. It also provides a copy-to-clipboard functionality for each token's address, with a brief notification appearing when an address is successfully copied.

Open the src/TokenList.js file and modify it as below. Feel free to check the comments for detailed information.

import React, { useState } from 'react'
import { viem } from '@quicknode/sdk'

function TokenList({ walletAddress, tokens }) {
const [notification, setNotification] = useState('') // State to handle notification messages

const handleCopyAddress = address => {
navigator.clipboard.writeText(address).then(() => {
// Asynchronously copy the address to clipboard
// Show notification message once the address is copied
setNotification({ message: 'Address copied to clipboard!' })

// Hide the notification after 1 second (1000 milliseconds)
setTimeout(() => {
setNotification('')
}, 1000)
})
}

return (
<div className="token-list">
{notification && (
// Display the notification message if there is one
<div className="notification">{notification.message}</div>
)}

<h2>Wallet {walletAddress}'s ERC20 Tokens</h2>

{/* Map through the tokens array and render each token's details */}
{tokens.length > 0 ? (
tokens.map(token => (
<div key={token.address} className="token-item">
<h2>
{token.name} ({token.symbol})
</h2>
{/* Format and display the token balance using viem.formatUnits */}
<p>
Balance:{' '}
{parseFloat(
viem.formatUnits(token.totalBalance, token.decimals)
).toFixed(2)}
</p>
<p>Contract Address: {token.address}</p>
{/* Button to copy the token contract address */}
<button onClick={() => handleCopyAddress(token.address)}>
Copy
</button>
</div>
))
) : (
<p>No ERC-20 Tokens found.</p>
)}
</div>
)
}

export default TokenList

App

Since the components are ready, it is time to bring them together and build the main file.

The src/App.js is the main structure for a React application that displays ERC20 token balances for a given wallet address. It includes functionality for searching and retrieving token balances from the Token and NFT API v2 bundle, pagination to navigate through results, and a loading state to indicate when data is being fetched.

Go to the src/App.js in your code editor and paste the following code. Feel free to check the comments for a detailed explanation.

Do not forget to replace YOUR_QUICKNODE_ENDPOINT_URL with your endpoint URL that you get in the Setting up QuickNode SDK section.

import './App.css' // Importing the CSS for styling

import React, { useState } from 'react' // Importing React and the useState hook
import SearchBar from './SearchBar' // Import SearchBar component
import TokenList from './TokenList' // Import TokenList component
import QuickNode from '@quicknode/sdk' // Import the QuickNode SDK

// Main App component
function App() {
// State variables for the application
const [walletAddress, setWalletAddress] = useState(""); // State to store the wallet address
const [tokens, setTokens] = useState([]); // State to store the list of tokens
const [currentPage, setCurrentPage] = useState(1); // State to store the current page number
const [pageInfo, setPageInfo] = useState(null); // State to store pagination information
const [loading, setLoading] = useState(false); // State to handle loading status
const [blockchain, setBlockchain] = useState("ethereum"); // State to store the selected blockchain, default to Ethereum

// Number of results to show per page
const resultsPerPage = 5;

// Initialize QuickNode Core with the QuickNode Endpoint
const core = new QuickNode.Core({
endpointUrl:
"YOUR_QUICKNODE_ENDPOINT_URL",
config: {
addOns: { nftTokenV2: true },
},
});

// Function to search for token balances by address
const searchAddress = async (
pagination = "firstPage" // Default pagination setting
) => {
setLoading(true); // Begin loading
try {
let data;

// Handle pagination based on the argument
switch (pagination) {
case "firstPage":
// Fetch token balances for the first page
data = await core.client.qn_getWalletTokenBalance({
wallet: walletAddress,
perPage: resultsPerPage,
});

break;

case "nextPage":
// Fetch token balances for the next page
data = await core.client.qn_getWalletTokenBalance({
wallet: walletAddress,
perPage: resultsPerPage,
page: pageInfo.pageNumber + 1,
});
break;

case "previousPage":
// Fetch token balances for the previous page
data = await core.client.qn_getWalletTokenBalance({
wallet: walletAddress,
perPage: resultsPerPage,
page: pageInfo.pageNumber - 1,
});
break;

default:
// Default case to handle other scenarios
data = await core.client.qn_getWalletTokenBalance({
wallet: walletAddress,
perPage: resultsPerPage,
page: pageInfo.pageNumber,
});
}

setTokens(data.result); // Update the tokens state with the fetched data
setPageInfo({ pageNumber: data.pageNumber, totalPages: data.totalPages }); // Update the pageInfo state with the fetched data
} catch (error) {
console.error("Error fetching token balances:", error); // Log any errors
}
setLoading(false); // End loading
};

// Function to navigate to the next page
const goToNextPage = () => {
if (pageInfo && pageInfo.pageNumber < pageInfo.totalPages) {
setCurrentPage(currentPage + 1); // Increment the current page number
searchAddress("nextPage"); // Fetch the next page of results
}
};

// Function to navigate to the previous page
const goToPreviousPage = () => {
if (pageInfo && pageInfo.pageNumber !== 1) {
setCurrentPage(currentPage - 1); // Increment the current page number
searchAddress("previousPage"); // Fetch the previous page of results
}
};

return (
<div className="App">
<header className="App-header">
<h1>ERC20 Token Balances</h1>
</header>
{/* Dropdown to select the blockchain */}
<select
value={blockchain}
onChange={(e) => setBlockchain(e.target.value)}
className="select-box"
>
<option value="ethereum">Ethereum</option>
</select>
{/* Search bar to input and search for wallet address */}
<SearchBar
onSearch={() => searchAddress("firstPage")}
inputValue={walletAddress}
onInputChange={setWalletAddress}
/>
{/* Conditional rendering to display either loading state or token list */}
{loading ? (
<p>Loading...</p> // Show loading text
) : (
<TokenList
walletAddress={walletAddress}
tokens={tokens}
page={currentPage}
pageSize={resultsPerPage}
/>
)}
{/* Pagination controls to navigate between pages */}
<div className="pagination">
<button
onClick={goToPreviousPage}
disabled={!pageInfo || currentPage === 1} // Disable button if no previous page
>
Previous
</button>
<span>{currentPage}</span>
<button
onClick={goToNextPage}
disabled={!pageInfo || currentPage === pageInfo.totalPages} // Disable button if no next page
>
Next
</button>
</div>
</div>
);
}

export default App;

Styling

The app is ready except for styling. Since styling using CSS is not a focus of this guide, we won't dive deep into each style set. However, feel free to play with the code and see its effects after you run the app locally in the following steps.

Open the src/App.css and replace the existing code in the file with the code below.

/* General styles */
body {
font-family: 'Arial', sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 20px;
}

.App {
max-width: 600px;
margin: auto;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}

.select-box {
margin-bottom: 1rem;
padding: 0.5rem 1rem;
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
font-size: 1rem;
cursor: pointer;
}

/* Search bar styles */
.search-bar {
padding: 10px;
margin-bottom: 20px;
width: 100%;
box-sizing: border-box;
border-radius: 4px;
border: 1px solid #ccc;
}

/* Token list styles */
.token-list {
list-style: none;
padding: 0;
}

.token-item {
padding: 15px;
background-color: #f9f9f9;
border: 1px solid #e1e1e1;
margin-bottom: 10px;
border-radius: 4px;
}

.token-item h2 {
margin-top: 0;
color: #333;
}

.token-item p {
margin: 5px 0;
}

.token-item button {
padding: 5px 10px;
background-color: #4caf50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}

.token-item button:hover {
background-color: #45a049;
}

/* Search button styles */
.search-bar + button {
padding: 10px 15px;
background-color: #008cba;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}

.search-bar + button:hover {
background-color: #007b9a;
}

.notification {
padding: 10px;
margin-bottom: 10px;
background-color: #4caf50;
color: white;
text-align: center;
border-radius: 4px;
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 1000;
}

/* Pagination styles */
.pagination {
display: flex;
justify-content: center;
margin-top: 20px;
}

.pagination button {
padding: 10px 15px;
margin: 0 5px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}

.pagination button:disabled {
background-color: #ccc;
}

.pagination button:not(:disabled):hover {
background-color: #0056b3;
}

.pagination span {
line-height: 30px;
margin: 0 10px;
}

Running the App

The coding portion is now finished! Let's start the application by running the following command.

npm start

If everything goes well, the output console should be similar to this.

Compiled successfully!

You can now view token-balance-app in the browser.

Local: http://localhost:3000
On Your Network: http://192.168.1.11:3000

Note that the development build is not optimized.
To create a production build, use npm run build.

webpack compiled successfully

Then, go to the http://localhost:3000 in your browser. It should look something like the left of the image below. After pasting an EVM address and searching it, the frontend should be like the right of the image.

Alt text

Check all functionalities like trying different addresses, and seeing more results by clicking the pagination buttons.

Conclusion

Congratulations on reaching the end of this guide! You've learned how to use the QuickNode SDK to build a React app that can display ERC20 token balances. As demonstrated, the QuickNode SDK simplifies your work by handling complex blockchain interactions, letting you concentrate on creating your app's core features.

To explore more capabilities of the QuickNode SDK and for any questions, check out the QuickNode SDK Documentation, join the QuickNode community on Discord, or reach out via Twitter.

We ❤️ Feedback!

Let us know if you have any feedback or requests for new topics. We'd love to hear from you.

Additional Resources


Share this guide