Marketplace has launched, further enabling blockchain developers! Learn more

How to Connect Users to Your dApp with the Solana Wallet Adapter and Scaffold

September 23, 2022

Overview

Been building on Solana and ready to bring your dApp to the web? You're going to need a way to connect your tools to your users' wallets. Though there are a few ways to connect your dApp to your users' wallets, Solana has created a couple of handy tools that make getting started easy: Solana Wallet Adapter and Solana dApp Scaffold.

What You Will Do

  • Learn what Solana Wallet Adapter is
  • Deploy the Solana dApp Scaffold
  • Customize your environment
  • Create a simple dApp to query a Connected Wallet 

What You Will Need

  • Nodejs (version 16.15 or higher)
  • A Solana wallet (e.g. Phantom)
  • Experience with [Solana Web3] and Solana SPL Token Library
  • Typescript experience and ts-node installed
  • Basic knowledge of HTML/CSS
  • Experience with front-end web development will be helpful but not required to follow along. We'll be creating a Next.js project using React. The Solana community also supports Vue, Angular, and Svelte. Visit their community page for more information.

What is the Wallet Adapter

Ever notice that many Solana dApps have a similar-looking UI for connecting to Wallets? 

Solana Wallet Adapter

That's the Solana Wallet Adapter ("SWA"), and you see it in lots of places because it is easy to use, inclusive of all of the most common wallets on Solana, and regularly maintained by the Solana Community. So what is it? The SWA is a set of Modular TypeScript wallet adapters and components for Solana applications that allow you to easily connect your dApp to your users' wallet of choice (over a dozen Solana wallets are supported out of the box!).

Here are some reasons to consider using SWA: 

  1. Open source, supported by Solana-labs
  2. Multi wallet support: let your users use their wallet of choice without a creating a bunch of headaches for you to support new wallets
  3. Includes an off-the-shelf customizable UI 
  4. Includes Anchor support
  5. Key Functionality built-in: Connect, Disconnect, Auto-connect
  6. Supports multiple front-end frameworks

Let's try it out!

Set Up Your Environment

SWA includes support for multiple front-end frameworks:
  • React
  • Material-UI, 
  • Ant Design
  • Angular Material UI
  • Vue (community-supported)
  • Angular (community-supported)
  • Svelte (community-supported)

You can add these packages to your existing projects using npm instructions here. For this example, however, we will be creating a new project using the Solana dApp Scaffold. The dApp Scaffold includes SWA and a few pre-built components to make it quick for you to get up and running!

Note: if you are adding the adapter to an existing project, the current SWA at the time of this writing does not support React 18. Please use React 17.

Create a new project directory in your terminal with:

set up your environment

Copy
mkdir my-solana-dapp
cd my-solana-dapp

Clone the dApp Scaffold: 

set up your environment

Copy
git clone https://github.com/solana-labs/dapp-scaffold.git . 

The `.` will clone the scaffold into your project directory without creating a new directory inside of it. 

Type ls in your terminal to make sure everything got copied correctly. Your terminal should look something like this:

Checkpoint Repo Cloned


Install dependencies: 

set up your environment

Copy
npm install
# or
yarn install

Also go ahead and add the SPL-token Library, which we'll use later in this exercise: 

set up your environment

Copy
npm i @solana/spl-token
# or
yarn add @solana/spl-token

Adding a QuickNode Custom RPC Endpoint

To build on Solana, you'll need an API endpoint to connect with the network. You're welcome to use public nodes (which are already integrated into the Scaffold) or deploy and manage your own infrastructure; however, if you'd like 8x faster response times, you can leave the heavy lifting to us. See why over 50% of projects on Solana choose QuickNode and sign up for a free, 7-day trial here.

We're going to launch our node under the Solana Devnet, but you can launch the node that meets your needs. Copy the HTTP Provider link:

New Solana Endpoint

Then, navigate back to your terminal, and create an .env file with the following command from your my-solana-dapp directory:

adding a quicknode custom rpc endpoint

Copy
echo > .env

We encourage using an .env to protect your key when creating a production site.

Open your project in a code editor of choice and navigate to your newly created .env file. Declare two variables, REACT_APP_SOLANA_RPC_HOST and REACT_APP_NETWORK. Paste your RPC url in the REACT_APP_SOLANA_RPC_HOST variable and set your REACT_APP_NETWORK to the Solana cluster you'll be using (devnet, mainnet-beta, or testnet). This should be the same network that you selected your RPC endpoint from. Your file should look something like this: 

adding a quicknode custom rpc endpoint

Copy
REACT_APP_SOLANA_RPC_HOST=https://example.solana-devnet.quiknode.pro/00000000000/
REACT_APP_NETWORK=devnet

We must also update next.config.js to use these environment variables in our Next.js app. Open the file in your root project directory and replace the contents with this: 

adding a quicknode custom rpc endpoint

Copy
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
}

module.exports = {
  nextConfig,   
  env: {
    REACT_APP_SOLANA_RPC_HOST: process.env.REACT_APP_SOLANA_RPC_HOST,
    REACT_APP_NETWORK: process.env.REACT_APP_NETWORK
  }
}

Now update the Scaffold's endpoint by opening ./src/contexts/ContextProvider.tsx and replacing lines 20-21:

adding a quicknode custom rpc endpoint

Copy
    const network = WalletAdapterNetwork.Devnet;
    const endpoint = useMemo(() => clusterApiUrl(network), [network]);
with

adding a quicknode custom rpc endpoint

Copy
    const network = process.env.REACT_APP_NETWORK as WalletAdapterNetwork;
    const endpoint = process.env.REACT_APP_SOLANA_RPC_HOST;

We're all set! Let's launch our app! 

adding a quicknode custom rpc endpoint

Copy
npm run dev
# or
yarn dev

If you have followed all of the steps to this point, you should be able to go to http://localhost:3000/ and see your scaffold. Nice work! You should see something like this: 

Solana Scaffold Landing Screen


Feel free to click around and explore the scaffold. You can Connect a Wallet and request an airdrop (devnet & testnet), sign a message, or send SOL to a random wallet. 

Orienting to Solana Wallet Adapter and Scaffold

Before making our component, let's look around our workspace to better understand how everything is working. Without going too deep into what is going on with the React here, let's cover a few essential pieces relevant to Solana dApps. 

Wallet Connect Button


Check out /my-solana-dapp/src/components/AppBar.tsx. You should see that we import WalletMultiButton from solana/wallet-adapter-react-ui. This button, <WalletMultiButton className="btn btn-ghost mr-4" /> is how the user interacts with our wallet adapter. You'll also notice here the use of the setAutoConnect method set the user toggle. We won't change anything here, but if you want to set or disable autoConnect you could use this form. 

Connection Context 


Next check out /my-solana-dapp/src/contexts/ContextProvider.tsx. This file is where we updated our endpoint and network earlier and is the home of the Wallet Context Provider, where we can configure the Wallet Adapter. 
  • First, notice our wallets variable. You'll notice a list of common Solana wallets listed. Try commenting one or more out with // or removing the comments on new SlopeWalletAdapter() (in the imports, lines 4-11 AND in the declarations, lines 22-35). Refresh your site and connect your wallet. You should notice that the list of available wallets has changed. Effectively, the wallets variable gets passed into the Wallet Adapter through the Wallet Provider Component to set which wallets will be allowed on our dApp.
  • autoconnect determines how the users' wallet interacts with the site on load. As we discussed earlier, our AppBar toggle allows the user to control this on the site UI.
  • onError tells our program how to handle errors
  • endpoint and network set how your app will connect with the Solana network
  • The Scaffold has come configured for us, but if you are setting up your project without the Scaffold, you'll need to make sure this context surrounds your app. You can see where this is done in /my-solana-dapp/src/pages/_app.tsx: <ContextProvider> is parent to our AppBar and ContentContainer.

Components


Before moving on, check out the Components folder, /my-solana-dapp/src/components. You'll see components associated with each of our app's pre-existing tools/buttons (e.g., Airdrop, Sign Message, Send Transaction). Feel free to explore a couple of these files to see how they work--we will be making our own in just a moment! If you're not familiar with React, that's fine. Just know that each of these files, or components, is basically a building block to our website. We drop those building blocks in the appropriate sites (for example, you can see the RequestAirdrop called in the Home View page: /my-solana-dapp/src/views/home/index.tsx).

Create a Custom Component for your dApp

Now that we have our dApp environment established and know how it works, let's add our component! For this exercise, we're going to use a script we developed in another tutorial to get all of the token accounts associated with an owner's wallet (accessible here). 

Create a Reusable Component Template


Let's make a component template that we can use for this exercise and that you can also use to easily create new components in the future.

Create a new file in the components directory called template.tsx. In your terminal, stop the server with a CTRL+C and enter the following command:

create a custom component for your dapp

Copy
echo > ./src/components/template.tsx

Open template.tsx and paste this code, then save. 

create a custom component for your dapp

Copy
import { useConnection, useWallet } from '@solana/wallet-adapter-react';
import { FC, useState } from 'react';
import { notify } from "../utils/notifications";
//Add import dependencies here

export const Template: FC = () => {
    const { connection } = useConnection();
    const { publicKey } = useWallet();

    //State Variables here

    //dApp Scripts here

    return(<div>

    {/* Render Results Here */}

    </div>)    
}

Set Up Your Component


This creates a shell for a React component that we can use to add our own functionality! Copy the template and save as a new file called GetTokens.tsx.

create a custom component for your dapp

Copy
cp ./src/components/template.tsx ./src/components/GetTokens.tsx

In GetTokens.tsx, rename the component by changing export const Template to: 

create a custom component for your dapp

Copy
export const GetTokens

For our tool that looks at a wallet's token accounts, we'll need to add a couple of additional dependencies to help us interact with the SPL Token Library. Add these under Add import dependencies here:

create a custom component for your dapp

Copy
import { GetProgramAccountsFilter } from '@solana/web3.js';
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";

Let's set up a new state variable, tokenTable using setState (if you're not familiar with React, that's fine--this is some of the magic that will help our results render to the page after we've found what we're looking for). Add the following code just after the State Variables here comment in the template:  

create a custom component for your dapp

Copy
    const [tokenTable, setTokenTable] = useState(null);

Create Your Query

We've gone ahead and modified the code from our existing QuickNode guide on How to Get All Tokens Held By a Wallet in Solana slightly to be React-friendly. Specifically instead of using console.log to display our results, we want to write the results to a table (as a JSX Element) that can be rendered on our website.
 
Go ahead and add this code underneath your state variable  //dApp Scripts Here and before return()

create a custom component for your dapp

Copy
    async function getTokenAccounts(wallet: string) {
        const filters:GetProgramAccountsFilter[] = [
            {
              dataSize: 165, // number of bytes
            },
            {
              memcmp: {
                offset: 32, // number of bytes
                bytes: wallet, // base58 encoded string
              },            
            }];
        const accounts = await connection.getParsedProgramAccounts(
            TOKEN_PROGRAM_ID, // new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
            {
              filters: filters,
            }
          );
        console.log(`Found ${accounts.length} token account(s) for wallet ${wallet}: `);
        if(accounts.length === 0) {
            return(<div>No Token Accounts Found</div>)
        }
        else{
            const rows = accounts.map((account,i)=>{
                //Parse the account data
                const parsedAccountInfo:any = account.account.data;
                const mintAddress:string = parsedAccountInfo["parsed"]["info"]["mint"];
                const tokenBalance: number = parsedAccountInfo["parsed"]["info"]["tokenAmount"]["uiAmount"];
                return (
                <tr key={i+1}>
                    <td key={'index'}>{i+1}</td>
                    <td key={'mint address'}>{mintAddress}</td>
                    <td key={'balance'}>{tokenBalance}</td>
                </tr>)
            })
            const header = (<tr>
                <th>Token No.</th>
                <th>Mint Address</th>
                <th>Qty</th>
            </tr>)
            setTokenTable(<table>{header}{rows}</table>)
        }
    }

You'll see that our .map method returns a <tr> row for every account found. When invoked, the getTokenAccounts method should fetch all token accounts from the chain, create a table element with the results, and use react to add the table element to our state. We've also added some logic to let the user know if there were no SPL token accounts found. 

Create a Click Handler


In order to invoke getTokenAccounts, create a function called onClick that we can hook to a button on our page. The button will:
  1. Check if a wallet is connected. We can do this by checking if publicKey is found from our useWallet() method from the Wallet Adapter.
  2. Try to getTokenAccounts for the connected wallet (note that we need to convert our public key to a string to use it in our filter parameters, using .toString()).
  3. Handle errors.

create a custom component for your dapp

Copy
    const onClick = async () => {
        if (!publicKey) {
            console.log('error', 'Wallet not connected!');
            notify({ type: 'error', message: 'error', description: 'Wallet not connected!' });
            return;
        }
        try { 
            await getTokenAccounts(publicKey.toString());

        } catch (error: any) {
            notify({ type: 'error', message: `Couldn't Find Token Accounts!`, description: error?.message });
            console.log('error', `Error finding Token Accounts! ${error?.message}`);
        }
    };

Create a Button


Inside of your component's return(), you'll see a <div>. Create a button inside of it that calls our onClick function. We're using the same CSS as used elsewhere in the template. Feel free to customize!

create a custom component for your dapp

Copy
        <div className="text-center">
        <button
                className="px-8 m-2 btn animate-pulse bg-gradient-to-r from-[#9945FF] to-[#14F195] hover:from-pink-500 hover:to-yellow-500"
                onClick={onClick}
            >
                <span>Get Token Accounts</span>
        </button>
        </div>

Render Results


Now display your results by calling the tokenTable state variable that we set with getTokenAccounts. Inside your div and after the Render Results Here comment, add:

create a custom component for your dapp

Copy
            <div>{tokenTable}</div>

Because we don't define a variable until after our token query, this will just be blank until the program has found anything. 

Here's what our Component looks like now that we're done: 

create a custom component for your dapp

Copy
import { useConnection, useWallet } from '@solana/wallet-adapter-react';
import { FC, useState } from 'react';
import { notify } from "../utils/notifications";
//Add import dependencies here
import { GetProgramAccountsFilter } from '@solana/web3.js';
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";

export const GetTokens: FC = () => {
    const { connection } = useConnection();
    const { publicKey } = useWallet();

    //State Variables here
    const [tokenTable, setTokenTable] = useState(null);

    //dApp Scripts here
    async function getTokenAccounts(wallet: string) {
        const filters:GetProgramAccountsFilter[] = [
            {
              dataSize: 165, // number of bytes
            },
            {
              memcmp: {
                offset: 32, // number of bytes
                bytes: wallet, // base58 encoded string
              },            
            }];
        const accounts = await connection.getParsedProgramAccounts(
            TOKEN_PROGRAM_ID, // new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
            {
              filters: filters,
            }
          );
        console.log(`Found ${accounts.length} token account(s) for wallet ${wallet}: `);
        if(accounts.length === 0) {
            return(<div>No Token Accounts Found</div>)
        }
        else{
            const rows = accounts.map((account,i)=>{
                //Parse the account data
                const parsedAccountInfo:any = account.account.data;
                const mintAddress:string = parsedAccountInfo["parsed"]["info"]["mint"];
                const tokenBalance: number = parsedAccountInfo["parsed"]["info"]["tokenAmount"]["uiAmount"];
                return (
                <tr key={i+1}>
                    <td key={'index'}>{i+1}</td>
                    <td key={'mint address'}>{mintAddress}</td>
                    <td key={'balance'}>{tokenBalance}</td>
                </tr>)
            })
            const header = (<tr>
                <th>Token No.</th>
                <th>Mint Address</th>
                <th>Qty</th>
            </tr>)
            setTokenTable(<table>{header}{rows}</table>)
        }

    }

    const onClick = async () => {
        if (!publicKey) {
            console.log('error', 'Wallet not connected!');
            notify({ type: 'error', message: 'error', description: 'Wallet not connected!' });
            return;
        }
        try { 
            await getTokenAccounts(publicKey.toString());

        } catch (error: any) {
            notify({ type: 'error', message: `Couldn't Find Token Accounts!`, description: error?.message });
            console.log('error', `Error finding Token Accounts! ${error?.message}`);
        }
    };

    return(<div>
        <div className="text-center">
        <button
                className="px-8 m-2 btn animate-pulse bg-gradient-to-r from-[#9945FF] to-[#14F195] hover:from-pink-500 hover:to-yellow-500"
                onClick={onClick}
            >
                <span>Get Token Accounts</span>
        </button>
        </div>
    {/* Render Results Here */}
        <div>{tokenTable}</div>
    </div>)
}

Great job! You've not built your first dApp component using the Solana Scaffold. Time to add it to the site.

Deploy Your Component

Now let's add our component to our homepage. Open up the home view, /my-solana-dapp/src/views/home/index.tsx. Since we created a new component, we must import it before using it. Add this import to line 14: 

deploy your component

Copy
import { GetTokens } from 'components/GetTokens';

Let's add our component below the existing airdrop button and wallet ballance display:

deploy your component

Copy
        <div className="text-center">
          <RequestAirdrop />
          {/* {wallet.publicKey && <p>Public Key: {wallet.publicKey.toBase58()}</p>} */}
          {wallet && <p>SOL Balance: {(balance || 0).toLocaleString()}</p>}
        </div>
        {/* ADD THIS 👇 */}
        <div>
          <GetTokens/>
        </div>

I don't know about you, but I'm ready to see this thing in action.  Let's run it!

deploy your component

Copy
npm run dev
# or
yarn dev

There's our beautiful button!

Get Token Accounts Button


If you get trigger happy like me, you will probably get an error message because you have not connected a wallet yet (remember when we set that error?):  

Wallet Not Connected Error


Connect your wallet by clicking Select Wallet in the top right corner. Select your preferred wallet. You should see your Solana balance once your wallet is connected.
Alright, NOW you can click "Get Token Accounts"... BOOM! Do you see something like this? 

Succesful Token Table


If you don't have any tokens in your wallet yet, you can check out our guide on minting NFTs with Candy Machine to get going.

Having fun? Feel free to continue building on this and even throw in some custom CSS to make that table look however you'd like!

Wrap Up

Congrats! You made it! And we have covered a lot. After completing this exercise, you can now build your own dApps with the Solana dApp Scaffold and Wallet Adapter. This is an excellent foundation for jumping into all sorts of things, so we will be building more components from this foundation in the future!  

Find this useful? Check out some of our other Solana tutorials here. Subscribe to our newsletter for more articles and guides on Solana. Feel free to reach out to us via Twitter if you have any feedback. You can always chat with us on our Discord community server, featuring some of the coolest developers you'll ever meet 😎

We ❤️ Feedback!
Wanna share your thoughts on this guide? We'd love to hear it!

Related articles 33

Como crear un NFT en SOLANA
Published: Dec 27, 2021
Updated: Sep 23, 2022

¡Hola querido lector! Bienvenidos a una nueva guía de Solana.Solana es una blockchain que promete mucho a la hora de intentar resolver los problemas de escalabilidad que...

Continue reading
How to Get Transaction Logs on Solana
Published: Jun 24, 2022
Updated: Oct 27, 2022

Ever need to pull all the transactions associated with a Wallet? Want to see all of the mint transactions associated with a Candy Machine? Or maybe see transaction history of an NFT? Solana's...

Continue reading
How to Transfer SPL Tokens on Solana
Published: Sep 23, 2022
Updated: Sep 23, 2022

Sending Solana Program Library (SPL) Tokens is a critical mechanism for Solana development. Whether you are airdropping whitelist tokens to your community, bulk sending NFTs to another wallet,...

Continue reading
How to Mint an NFT on Solana
Published: Aug 27, 2021
Updated: Sep 23, 2022

Updated at: April 10, 2022Welcome to another QuickNode guide on Solana - the up-and-coming blockchain that seeks to solve the scalability issues of Ethereum. We will be walking through...

Continue reading
How to Send Bulk Transactions on Solana
Published: Aug 31, 2022
Updated: Oct 3, 2022

Are you running a batch process that has many transactions? Perhaps an airdrop to your community's NFT holders or a token distribution to early users of your dApp. Solana transaction...

Continue reading
Solana Fundamentals Reference Guide
Published: Oct 27, 2022
Updated: Oct 27, 2022

The Solana blockchain is a powerful tool, delivering thousands of transactions per second with almost no-cost transaction fees. If you are new to Web3 or have developed on EVM-based...

Continue reading