Solana
  • Solana Development
    • Prerequisites
    • Getting Started
      • ShadCN
      • Getting started with Next.js
    • Developing on Solana
      • Minting Solana NFTs: A Beginner's Guide
  • Examples
    • Creating a Custom Solana Wallet Connect UI
    • Portfolio Applicaiton
    • Solana Wallet Adapter
    • JavaScript Client for Mpl Core
  • Solana Cookbook
  • Wonka.JS and next.js
  • Metaplex Sugar
  • Solana Wallet Adapter To a Next.Js application
    • Basic Wallet Adapter with React
    • Integrating Solana Wallet Adapter in a Next.js Application
    • Solana Wallet Example Code and Tutorial
    • Git Hub Code
  • Token Burning Candy Machine
  • Page 1
  • DLMM
  • Page 2
  • React And Next.JS
    • Material UI
    • Installation
    • Usage
    • Example projects
Powered by GitBook
On this page
  1. Examples

Creating a Custom Solana Wallet Connect UI

Creating a Custom Solana Wallet Connect UI with Next.js, Tailwind, and Shadcn

While solutions like the Solana Wallet Adapter provide a straightforward way to implement wallet connectivity, their customization options are often limited, leaving developers seeking more flexibility in their user interface designs. In this article, we delve into the realm of custom wallet connectors for Solana, exploring the @solana/wallet-adapter-react package and its associated UI library.

Setting Up the Next.js App with Tailwind CSS

1. Create a New Next.js Project:

Run the following command to initialize your Next.js application:

npx create-next-app@latest

After running this command, you’ll be prompted to set up various configurations such as TypeScript, ESLint, Tailwind CSS, and more.

2. Initial File Structure:

After the setup, your initial file structure should resemble something like this:

• .next/

• app/

• components/

• lib/

• node_modules/

• providers/

• public/

• .eslintrc.json

• .gitignore

• components.json

• next-env.d.ts

• next.config.mjs

• package-lock.json

• package.json

• postcss.config.js

• README.md

• tailwind.config.ts

• tsconfig.json

Setting Up Shadcn

Shadcn provides reusable components that are compatible with Tailwind CSS. It’s an open-source project that simplifies UI component integration.

1. Initialize Shadcn:

Run the following command:

npx shadcn-ui@latest init

2. Install Required NPM Libraries:

To create the wallet connector, install the following libraries:

npm i @solana/wallet-adapter-react @solana/wallet-adapter-react-ui @solana/web3.js @solana/wallet-adapter-wallets @solana/wallet-adapter-base

3. Add the Shadcn Dialog Component:

Run the following command to add the Dialog component:

npx shadcn-ui@latest add dialog

Coding the Wallet Connect Context Provider

Let’s create the wallet connect context provider and wrap the entire app with it.

"use client";

import { FC, ReactNode } from "react";
import {
  ConnectionProvider,
  WalletProvider,
} from "@solana/wallet-adapter-react";
import { WalletModalProvider } from "@solana/wallet-adapter-react-ui";
import "@solana/wallet-adapter-react-ui/styles.css";
import { clusterApiUrl } from "@solana/web3.js";
import {
  PhantomWalletAdapter,
  SolflareWalletAdapter,
  MathWalletAdapter,
  TrustWalletAdapter,
  CoinbaseWalletAdapter,
} from "@solana/wallet-adapter-wallets";
import { useMemo } from "react";
import { WalletAdapterNetwork } from "@solana/wallet-adapter-base";
import { useWallet } from "@solana/wallet-adapter-react";

const WalletContextProvider: FC<{ children: ReactNode }> = ({ children }) => {
  const network = WalletAdapterNetwork.Devnet;

  // Initiate auto-connect
  const { autoConnect } = useWallet();

  // Provide a custom RPC endpoint
  const endpoint = useMemo(() => clusterApiUrl(network), [network]);

  // Wallets
  const wallets = useMemo(
    () => [
      new PhantomWalletAdapter(),
      new SolflareWalletAdapter(),
      new MathWalletAdapter(),
      new TrustWalletAdapter(),
      new CoinbaseWalletAdapter(),
    ],
    [network]
  );

  return (
    <ConnectionProvider endpoint={endpoint}>
      <WalletProvider wallets={wallets} autoConnect>
        <WalletModalProvider>{children}</WalletModalProvider>
      </WalletProvider>
    </ConnectionProvider>
  );
};

export default WalletContextProvider;

Explanation:

• Network Selection: The network is set to Devnet, which is a development network provided by Solana.

• Auto Connect: The useWallet hook from @solana/wallet-adapter-react is used to automatically connect to the wallet when the component is loaded.

• RPC Endpoint Setup: The clusterApiUrl function is used to get the RPC endpoint for the selected network, and this is memoized using React’s useMemo.

• Wallet Adapters Setup: An array of wallet adapters for different wallets is memoized.

• Context Providers: The context providers are set up in a hierarchy to provide the RPC connection context (ConnectionProvider), wallet context (WalletProvider), and wallet modal context (WalletModalProvider).

Wrapping the App with WalletContextProvider

Wrap the entire app using WalletContextProvider in layout.tsx:

import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import Header from "./components/Header";
import WalletContextProvider from "@/providers/WalletContextProvider";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <WalletContextProvider>
          <Header />
          {children}
        </WalletContextProvider>
      </body>
    </html>
  );
}

Creating a Header Component

Create a simple Header component to contain the wallet connect button:

import React from "react";
import WalletConnection from "./WalletConnection";

const Header = () => {
  return (
    <div className="h-[10vh] bg-black flex justify-center">
      <div className="max-w-[900px] flex justify-between items-center w-full">
        <div className="text-white font-bold text-[30px]">Hello</div>
        <div>
          <WalletConnection />
        </div>
      </div>
    </div>
  );
};

export default Header;

Adding Shadcn Components for the Wallet Connect Button

Run the following commands to add the Button and Dropdown Menu components:

npx shadcn-ui@latest add button
npx shadcn-ui@latest add dropdown-menu

Writing the Wallet Connect Button UI and Functionalities

"use client";
import React, { useEffect, useState } from "react";
import { Button } from "./ui/button";
import { useConnection, useWallet } from "@solana/wallet-adapter-react";
import { LAMPORTS_PER_SOL } from "@solana/web3.js";
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
import Image from "next/image";
import { ChevronRight } from "lucide-react";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";

// Handle wallet balance fixed to 2 decimal numbers without rounding
export function toFixed(num: number, fixed: number): string {
  const re = new RegExp(`^-?\\d+(?:\\.\\d{0,${fixed || -1}})?`);
  return num.toString().match(re)![0];
}

const WalletConnection = () => {
  const { connection } = useConnection();
  const { select, wallets, publicKey, disconnect, connecting } = useWallet();

  const [open, setOpen] = useState<boolean>(false);
  const [balance, setBalance] = useState<number | null>(null);
  const [userWalletAddress, setUserWalletAddress] = useState<string>("");

  useEffect(() => {
    if (!connection || !publicKey) {
      return;
    }

    connection.onAccountChange(
      publicKey,
      (updatedAccountInfo) => {
        setBalance(updatedAccountInfo.lamports / LAMPORTS_PER_SOL);
      },
      "confirmed"
    );

    connection.getAccountInfo(publicKey).then((info) => {
      if (info) {
        setBalance(info?.lamports / LAMPORTS_PER_SOL);
      }
    });
  }, [publicKey, connection]);

  useEffect(() => {
    setUserWalletAddress(publicKey?.toBase58()!);
  }, [publicKey]);

  const handleWalletSelect = async (walletName: any) => {
    if (walletName) {
      try {
        select(walletName);
        setOpen(false);
      } catch (error) {
        console.log("wallet connection err : ", error);
      }
    }
  };

  const handleDisconnect = async () => {
    disconnect();
  };

  return (
    <div className="text-white">
      <Dialog open={open} onOpenChange={setOpen}>
        <div className="flex gap-2 items-center">
          {!publicKey ? (
            <>
              <DialogTrigger asChild>
                <Button className="bg-black text-[20px] md:text-[30px] text-white ring-black ring-2 h

PreviousMinting Solana NFTs: A Beginner's GuideNextPortfolio Applicaiton

Last updated 9 months ago