Murphy Logo Murphy

Connect Wallet Button

A Solana wallet connection component with shadcn UI styling

Connect Wallet Button

Installation

Install dependencies

Start by installing solana/wallet-adapter-base and wallet-adapter-wallets

pnpm add @solana/wallet-adapter-base @solana/wallet-adapter-wallets

Create a wallet provider

components/wallet-provider.tsx

import React, { useState, useMemo, createContext, useCallback } from "react";
import { WalletAdapterNetwork } from "@solana/wallet-adapter-base";
import type { Adapter } from "@solana/wallet-adapter-base";
import {
  WalletProvider as SolanaWalletProvider,
  ConnectionProvider as SolanaConnectionProvider,
  ConnectionProviderProps,
} from "@solana/wallet-adapter-react";
import { PhantomWalletAdapter } from "@solana/wallet-adapter-wallets";
 
// Create wrapper components
// @ts-ignore - Ignore React 19 compatibility error
const ConnectionProviderWrapper = (props: ConnectionProviderProps) => (
  <SolanaConnectionProvider {...props} />
);
// @ts-ignore - Ignore React 19 compatibility error
const WalletProviderWrapper = (props: any) => (
  <SolanaWalletProvider {...props} />
);
 
interface WalletProviderProps {
  children: React.ReactNode;
  network?: WalletAdapterNetwork;
  endpoint?: string;
  wallets?: Adapter[];
  autoConnect?: boolean;
}
 
interface ModalContextState {
  isOpen: boolean;
  setIsOpen: (open: boolean) => void;
  endpoint?: string;
  switchToNextEndpoint: () => void;
  availableEndpoints: string[];
  currentEndpointIndex: number;
  isMainnet: boolean;
}
 
export const ModalContext = createContext<ModalContextState>({
  isOpen: false,
  setIsOpen: () => null,
  endpoint: undefined,
  switchToNextEndpoint: () => null,
  availableEndpoints: [],
  currentEndpointIndex: 0,
  isMainnet: true,
});
 
export const WalletProvider = ({ children, ...props }: WalletProviderProps) => {
  // Add state to store endpoints and current endpoint
  const [currentEndpointIndex, setCurrentEndpointIndex] = useState(0);
 
  // Determine if we're using mainnet or devnet based on environment variable
  const isMainnet = useMemo(() => {
    const mainnetEnv = process.env.NEXT_PUBLIC_USE_MAINNET;
    return mainnetEnv === undefined ? true : mainnetEnv === "true";
  }, []);
 
  // Network type based on the environment variable
  const networkType = useMemo(
    () =>
      isMainnet ? WalletAdapterNetwork.Mainnet : WalletAdapterNetwork.Devnet,
    [isMainnet]
  );
 
  // List of public RPC endpoints based on network type
  const publicRPCs = useMemo(
    () => [
      isMainnet
        ? (process.env.NEXT_PUBLIC_SOLANA_RPC_URL as string)
        : (process.env.NEXT_PUBLIC_SOLANA_RPC_URL_DEVNET as string),
    ],
    [isMainnet]
  );
 
  const defaultNetwork = useMemo(
    () => props.network || networkType,
    [props.network, networkType]
  );
 
  // Provided endpoint will be prioritized, otherwise use the current endpoint from the list
  const endpoint = useMemo(() => {
    if (props.endpoint) {
      return props.endpoint;
    }
    return publicRPCs[currentEndpointIndex];
  }, [props.endpoint, publicRPCs, currentEndpointIndex]);
 
  // Function to switch to the next endpoint when an error occurs
  const switchToNextEndpoint = useCallback(() => {
    setCurrentEndpointIndex((prevIndex) => {
      const nextIndex = (prevIndex + 1) % publicRPCs.length;
      console.log(
        `Switching RPC endpoint from ${publicRPCs[prevIndex]} to ${publicRPCs[nextIndex]}`
      );
      return nextIndex;
    });
  }, [publicRPCs]);
 
  const wallets = useMemo(
    () => props.wallets || [new PhantomWalletAdapter()],
    [props.wallets]
  );
  const [isOpen, setIsOpen] = useState(false);
 
  console.log(
    `Using Solana ${isMainnet ? "mainnet" : "devnet"} endpoint: ${endpoint} (${
      currentEndpointIndex + 1
    }/${publicRPCs.length})`
  );
 
  return (
    <ModalContext.Provider
      value={{
        isOpen,
        setIsOpen,
        endpoint,
        switchToNextEndpoint,
        availableEndpoints: publicRPCs,
        currentEndpointIndex,
        isMainnet,
      }}
    >
      <ConnectionProviderWrapper endpoint={endpoint}>
        <WalletProviderWrapper wallets={wallets} autoConnect>
          {children}
        </WalletProviderWrapper>
      </ConnectionProviderWrapper>
    </ModalContext.Provider>
  );
};

Create .env file

.env

# Use mainnet or testnet
NEXT_PUBLIC_USE_MAINNET=false
# Solana RPC URLs , alchemy.com
NEXT_PUBLIC_SOLANA_RPC_URL="https://solana-mainnet.g.alchemy.com/v2/your-alchemy-api-key"
NEXT_PUBLIC_SOLANA_RPC_URL_DEVNET="https://solana-devnet.g.alchemy.com/v2/your-alchemy-api-key"

Wrap your root layout

Add the Wall to your root layout

import { ThemeProvider } from "@/components/wallet-provider.tsx";
 
export default function RootLayout({ children }: RootLayoutProps) {
  return (
    <>
      <html lang="en" suppressHydrationWarning>
        <head />
        <body>
          <WalletProvider autoConnect>
            {children}
            <Toaster />
          </WalletProvider>
        </body>
      </html>
    </>
  );
}

Add Connect Wallet Button

pnpm dlx shadcn@canary add https://www.murphyai.dev/r/connect-wallet-button.json

Basic Usage

import { ConnetWalletButton } from "@/components/ui/murphy/connect-wallet-button";
import { Wallet } from "lucide-react";
export default function MyPage() {
  return (
    <div>
      <h1 className="text-xl font-bold mb-2">Connect Your Solana Wallet</h1>
      <ConnetWalletButton>
        <Wallet className="size-4 mr-2" />
        Connect Wallet
      </ConnetWalletButton>
    </div>
  );
}

Customization

You can customize the button appearance by passing props:

<ConnetWalletButton variant="outline" size="lg" className="my-custom-class" />
PropTypeDefault
variant?
string
default
size?
string
default
className?
string
-
asChild?
boolean
false

On this page