Hello again! In our last lesson, we successfully integrated the wallet adapter and learned how to use the useWallet hook to access the user's connection status and public key. This gave our application "wallet awareness."
Now, we'll take the next crucial step: using that public key to read live data from the Solana blockchain. This lesson is all about fetching and displaying the two most common types of balances a user has: their native SOL balance and their SPL token balances (like USDC, RAY, etc.). This will be your first time pulling meaningful, user-specific data from the network into your dApp's UI.
Fetching the Native SOL Balance
First, let's get the user's SOL balance. To interact with the blockchain, we need two things we learned about in the previous lesson: the user's publicKey and a connection to a Solana RPC node. The wallet adapter makes both available through hooks.
You're already familiar with useWallet. Now, let's introduce its partner: useConnection.
useWallet(): Gives you information about the wallet itself, likepublicKeyandconnectedstatus.useConnection(): Gives you aConnectionobject from@solana/web3.js, which is your gateway for making RPC calls to the network.
With both connection and publicKey, we can fetch the balance. The process, which will be very familiar to you from your React experience, looks like this:
- Use a
useEffecthook to run code whenever thepublicKeychanges (i.e., when a user connects or disconnects). - Inside the
useEffect, check that you have both a validconnectionandpublicKey. - Call the asynchronous function
connection.getBalance(publicKey). - The result is a number representing the balance in Lamports, the smallest unit of SOL (1 SOL = 1,000,000,000 Lamports).
- Convert this value to SOL by dividing it by
LAMPORTS_PER_SOL, a constant provided by@solana/web3.js. - Store this SOL balance in a state variable using
useStateand display it in your UI.
The following video provides an excellent walkthrough of this entire process.
Use The Solana Wallet Adapter (Next.js, TypeScript, Tailwind CSS) • Solana Frontend Developer Course
This segment from the Helius developer course demonstrates exactly how to fetch and display a user's SOL balance. It covers using the hooks, triggering the fetch, and converting from Lamports.
Watch the section from 00:15:50 to 00:20:16. Pay close attention to how useEffect is set up to depend on connection and publicKey, and how the balance is calculated from the result of the RPC call. Note that the video uses getAccountInfo and reads the lamports property; a more direct method is getBalance(), but the underlying principle is identical.
Here's a concise code example implementing this logic using connection.getBalance():
import React, { useEffect, useState } from 'react';
import { useConnection, useWallet } from '@solana/wallet-adapter-react';
import { LAMPORTS_PER_SOL } from '@solana/web3.js';
export const SolBalanceDisplay = () => {
const { connection } = useConnection();
const { publicKey } = useWallet();
const [solBalance, setSolBalance] = useState<number | null>(null);
useEffect(() => {
if (!connection || !publicKey) {
setSolBalance(null); // Clear balance if wallet disconnects
return;
}
// A controller to abort the fetch if the component unmounts
const controller = new AbortController();
const { signal } = controller;
const fetchBalance = async () => {
try {
const balance = await connection.getBalance(publicKey, 'confirmed', { signal });
setSolBalance(balance / LAMPORTS_PER_SOL);
} catch (error) {
if (error.name !== 'AbortError') {
console.error("Error fetching balance:", error);
setSolBalance(null);
}
}
};
fetchBalance();
return () => {
// Abort the ongoing fetch if the component unmounts or dependencies change
controller.abort();
};
}, [connection, publicKey]);
return (
<div>
<h4>SOL Balance:</h4>
{publicKey ? (
<p>{solBalance !== null ? `${solBalance.toFixed(4)} SOL` : 'Loading...'}</p>
) : (
<p>Please connect your wallet.</p>
)}
</div>
);
};
Test your understanding!
In the code example above, why is it important to include both connection and publicKey in the dependency array of the useEffect hook? What would happen if we only included publicKey?
Show answer
It's crucial to include both because the fetchBalance function depends on both variables. If we only included publicKey, the effect would run when the user connects their wallet. However, the connection object might not be initialized at the exact same moment. By including both, we ensure the useEffect re-runs whenever either of these critical dependencies changes, guaranteeing that we always have the most recent, valid objects before attempting to make an API call. This makes the component more robust and predictable.
Fetching SPL Token Balances
Fetching SPL token balances is more involved. A user's wallet doesn't directly hold tokens. Instead, for each type of token (e.g., USDC, USDT), the user has a separate Token Account. All of these token accounts are owned by the user's main wallet publicKey.
Therefore, the process is:
- Ask the Solana network for all token accounts owned by the user's
publicKey. - Iterate through the results to get the balance and token type for each.
The @solana/web3.js library provides a specific function for this: connection.getParsedTokenAccountsByOwner(). We provide it the user's publicKey and filter for accounts associated with the official TOKEN_PROGRAM_ID.
The result gives us a list, where each item contains the balance and the mint address of the token. A mint address is a unique identifier for a specific token type. To make this user-friendly (e.g., showing "USD Coin" and its logo instead of a long address), we need to cross-reference this mint address with a token registry.
The following article provides an excellent, comprehensive guide to implementing this. It builds a BalanceProvider that encapsulates the logic for fetching both SOL and SPL balances.
Mastering Solana Wallet Integration: Fetching SOL and ...
This article, 'Mastering Solana Wallet Integration', details a robust pattern for fetching all of a user's balances. We'll focus on the implementation of the BalanceProvider component.
Please read the section titled 'Fetching SOL and SPL Token Balances'. Focus on the BalanceProvider.tsx code block. Observe how it uses connection.getBalance for SOL and connection.getParsedTokenAccountsByOwner for SPL tokens within the same useEffect. Also, note the use of the useTokenData hook, which fetches a token list to map mint addresses to symbols and logos.
Displaying the Balances in the UI
Once you have fetched the data, either in a component's local state or through a React Context like in the article above, you can display it. The pattern is standard React: map over your array of token balances and render a component for each one.
The same article provides a clean example of a UserBalances component that consumes the context and renders the data.
Mastering Solana Wallet Integration: Fetching SOL and ...
Now let's see how to display the data fetched by the BalanceProvider.
Review the section 'UserBalances Component'. This shows how a consumer component can use a custom hook (useBalanceContext) to access the loading state, SOL balance, and the array of token balances to render a complete view for the user.
This pattern of separating data-fetching logic (in a provider or custom hook) from presentation logic (in display components) is a cornerstone of building clean and maintainable React applications, a practice you're surely well-versed in.
Conclusion
In this lesson, you've bridged the gap between your dApp and live on-chain data. You now have the fundamental skills to query and display a user's assets.
Key Takeaways:
- The
useConnectionhook provides theConnectionobject needed for RPC calls. - Fetch native SOL balance with
connection.getBalance(publicKey)and convert the result from Lamports usingLAMPORTS_PER_SOL. - Fetch SPL token balances by finding all associated token accounts with
connection.getParsedTokenAccountsByOwner(). - The
useEffecthook, triggered by changes inpublicKeyandconnection, is the standard pattern for initiating these data fetches. - A token registry (like
@solana/spl-token-registry) is essential for translating token mint addresses into user-friendly names and logos, greatly improving the user experience.
Preview of the Next Lesson:
We've successfully read data from the blockchain. The next step is to write to it. In the upcoming lesson, "Build a UI form to gather user input for a program instruction," we will start preparing to send transactions. You'll learn how to create forms in React that capture the necessary information from a user to construct and ultimately sign a transaction.
Can't find a good explanation? Sign up and we'll make it for you
Sign up