1.0.8 โ€ข Published 10 months ago

@hermis/solana-headless-react v1.0.8

Weekly downloads
-
License
Apache-2.0
Repository
-
Last release
10 months ago

@hermis/solana-headless-react

๐Ÿงช Live Demo

Try our playground:

View the Demo โ†’

๐ŸŒŸ Overview

@hermis/solana-headless-react provides a complete set of React hooks and components for integrating Solana wallet functionality into React applications. Built on top of the core @hermis/solana-headless-core package, it allows developers to implement wallet connection, transaction signing, and blockchain interactions with full UI flexibility.

โœจ Features

  • UI Agnostic: Build your own UI components without any design constraints
  • Modern React Hooks: Intuitive hooks-based API for wallet and connection management
  • Full TypeScript Support: Complete type definitions for better development experience
  • Complete Wallet Management: Support for all major Solana wallets
  • Multi-Framework Support: Works with React, Next.js, Vite, and any React-based framework
  • Persistence: Local storage integration for wallet connection persistence

๐Ÿ“ฆ Installation

# Using npm
npm install @hermis/solana-headless-react @hermis/solana-headless-core

# Using yarn
yarn add @hermis/solana-headless-react @hermis/solana-headless-core

# Using pnpm
pnpm add @hermis/solana-headless-react @hermis/solana-headless-core

You'll also need to install the wallet adapters you want to support:

# Install wallet adapters
npm install @solana/wallet-adapter-wallets

๐Ÿš€ Quick Start

Basic Setup with HermisProvider

// App.tsx
import React from 'react';
import { HermisProvider } from '@hermis/solana-headless-react';
import { WalletAdapterNetwork } from '@hermis/solana-headless-core';
import {
  PhantomWalletAdapter,
  SolflareWalletAdapter,
  TrustWalletAdapter,
  CoinbaseWalletAdapter,
} from "@solana/wallet-adapter-wallets";
import Home from './Home';

function App() {
  // Create wallet adapters
  const wallets = [
    new PhantomWalletAdapter(),
    new SolflareWalletAdapter(),
    new TrustWalletAdapter(),
    new CoinbaseWalletAdapter(),
  ];

  return (
    <HermisProvider
      rpcEndpoint="https://api.devnet.solana.com"
      network={WalletAdapterNetwork.Devnet}
      autoConnect={true}
      additionalAdapters={wallets}
      onError={errorHandler}
    >
      <Home />
    </HermisProvider>
  );
}

export default App;

Using Wallet Hooks

// Home.tsx
import React from 'react';
import { useWallet, useSolanaBalance } from '@hermis/solana-headless-react';

function Home() {
  const { 
    wallet, 
    publicKey, 
    connecting, 
    connected, 
    connect, 
    disconnect, 
    select 
  } = useWallet();
  
  const { balance, loading } = useSolanaBalance(publicKey);

  const handleConnect = async () => {
    if (wallet) {
      await connect();
    } else {
      // Select a wallet first if none is selected
      select('Phantom');
      await connect();
    }
  };

  return (
    <div>
      <h1>Solana Wallet Demo</h1>
      
      {!connected ? (
        <div>
          <button onClick={handleConnect} disabled={connecting}>
            {connecting ? 'Connecting...' : 'Connect Wallet'}
          </button>
        </div>
      ) : (
        <div>
          <p>Connected with: {wallet?.adapter.name}</p>
          <p>Public Key: {publicKey?.toBase58()}</p>
          <p>Balance: {loading ? 'Loading...' : `${balance} SOL`}</p>
          <button onClick={disconnect}>Disconnect</button>
        </div>
      )}
    </div>
  );
}

export default Home;

โš›๏ธ Next.js Integration

For Next.js applications, you'll need to handle the client-side rendering aspect of wallet connections.

Next.js Provider Setup (App Router)

// app/providers.tsx
'use client';

import { HermisProvider } from '@hermis/solana-headless-react';
import { PhantomWalletAdapter } from '@solana/wallet-adapter-phantom';
import { SolflareWalletAdapter } from '@solana/wallet-adapter-solflare';
import { WalletAdapterNetwork } from '@hermis/solana-headless-core';
import { ReactNode } from 'react';

export function Providers({ children }: { children: ReactNode }) {
  // Create wallet adapters
  const wallets = [
    new PhantomWalletAdapter(),
    new SolflareWalletAdapter(),
  ];

  return (
    <HermisProvider
      rpcEndpoint="https://api.devnet.solana.com"
      network={WalletAdapterNetwork.Devnet}
      autoConnect={true}
      additionalAdapters={wallets}
    >
      {children}
    </HermisProvider>
  );
}

Next.js App Layout

// app/layout.tsx
import { Providers } from './providers';

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

Next.js Client Component

// app/wallet-component.tsx
'use client';

import { useWallet, useSolanaBalance } from '@hermis/solana-headless-react';

export default function WalletComponent() {
  const { 
    wallet, 
    publicKey, 
    connected, 
    connect, 
    disconnect 
  } = useWallet();
  
  const { balance, loading } = useSolanaBalance(publicKey);

  return (
    <div>
      {!connected ? (
        <button onClick={connect}>Connect Wallet</button>
      ) : (
        <div>
          <p>Connected to {wallet?.adapter.name}</p>
          <p>Public Key: {publicKey?.toBase58()}</p>
          <p>Balance: {loading ? 'Loading...' : `${balance} SOL`}</p>
          <button onClick={disconnect}>Disconnect</button>
        </div>
      )}
    </div>
  );
}

Next.js Page

// app/page.tsx
import WalletComponent from './wallet-component';

export default function Home() {
  return (
    <main>
      <h1>Solana Wallet Integration</h1>
      <WalletComponent />
    </main>
  );
}

๐Ÿ”Œ Core Components

HermisProvider

The main provider component that sets up all Solana wallet functionality. It combines both the connection provider and wallet provider.

<HermisProvider
  rpcEndpoint="https://api.devnet.solana.com"
  network={WalletAdapterNetwork.Devnet}
  autoConnect={true}
  additionalAdapters={wallets}
  storageKey="my-app-wallet"
  onError={(error) => console.error(error)}
>
  {children}
</HermisProvider>

Props:

  • rpcEndpoint: (string) The Solana RPC endpoint to connect to
  • network: (WalletAdapterNetwork) The Solana network (Mainnet, Devnet, Testnet)
  • autoConnect: (boolean) Whether to automatically connect to the last used wallet
  • additionalAdapters: (Adapter[]) Wallet adapters to use
  • storageKey: (string) Key for storing wallet name in local storage
  • storageFactory: (StorageProviderFactory) Custom storage factory for persistence
  • onError: (function) Error handler for wallet errors

WalletConnectionManager

A render props component for managing wallet connections. This is useful when you want to create custom wallet connection UI components.

<WalletConnectionManager>
  {({
    connecting,
    connected,
    publicKey,
    walletName,
    walletIcon,
    connect,
    disconnect,
    selectWallet
  }) => (
    <div>
      {/* Custom wallet UI */}
    </div>
  )}
</WalletConnectionManager>

๐Ÿงฐ React Hooks

useWallet

The primary hook for wallet interactions, providing state and methods for wallet management.

const {
  autoConnect,         // Whether auto-connect is enabled
  wallets,             // Array of available wallets
  wallet,              // The currently selected wallet
  publicKey,           // Public key of the connected wallet
  connecting,          // Whether wallet is connecting
  connected,           // Whether wallet is connected
  disconnecting,       // Whether wallet is disconnecting
  select,              // Function to select a wallet by name
  connect,             // Function to connect to the selected wallet
  disconnect,          // Function to disconnect the wallet
  sendTransaction,     // Function to send a transaction
  signTransaction,     // Function to sign a transaction
  signAllTransactions, // Function to sign multiple transactions
  signMessage,         // Function to sign a message
  signIn,              // Function for Sign-In With Solana
  hasFeature           // Function to check wallet capability
} = useWallet();

useConnection

Hook for accessing the Solana connection instance.

const { connection, network } = useConnection();

// Example: Get account info
const getAccountInfo = async (publicKey) => {
  const info = await connection.getAccountInfo(publicKey);
  return {
        ...info,
        network,
    };
};

useSolanaBalance

Hook for fetching and tracking a wallet's SOL balance.

const { 
  balance,         // Balance in SOL
  balanceLamports, // Balance in lamports
  loading,         // Whether balance is loading
  error,           // Error, if any occurred
  refetch          // Function to manually refetch balance
} = useSolanaBalance(publicKey, 10000); // 10000ms refresh interval

useWalletAdapters

Hook for working with wallet adapters, including sorting and filtering.

const { 
  installed,     // Installed wallet adapters
  loadable,      // Loadable wallet adapters
  notDetected,   // Not detected wallet adapters
  all            // All wallet adapters
} = useWalletAdapters();

// Example: Render installed wallets
return (
  <div>
    <h2>Installed Wallets</h2>
    <ul>
      {installed.map(adapter => (
        <li key={adapter.name}>
          {adapter.name}
        </li>
      ))}
    </ul>
  </div>
);

useSolanaTokenAccounts

Hook for fetching and tracking SPL token accounts owned by a wallet address.

const {
  tokenAccounts, // Array of token accounts
  loading,       // Whether data is loading
  error,         // Error, if any occurred
  refetch        // Function to manually refetch data
} = useSolanaTokenAccounts(publicKey);

// Example: Display token balances
return (
  <div>
    <h2>Token Balances</h2>
    {loading ? (
      <p>Loading tokens...</p>
    ) : tokenAccounts.length === 0 ? (
      <p>No tokens found</p>
    ) : (
      <ul>
        {tokenAccounts.map(account => {
          const displayAmount = Number(account.amount) / Math.pow(10, account.decimals);
          return (
            <li key={account.mint.toString()}>
              {displayAmount} ({account.mint.toString().slice(0, 6)}...)
            </li>
          );
        })}
      </ul>
    )}
  </div>
);

useSolanaNFTs

Hook for fetching and displaying NFTs owned by a wallet address.

const {
  nfts,    // Array of NFTs
  loading, // Whether data is loading
  error,   // Error, if any occurred
  refetch  // Function to manually refetch data
} = useSolanaNFTs(publicKey);

// Example: Display NFTs
return (
  <div>
    <h2>My NFTs</h2>
    {loading ? (
      <p>Loading NFTs...</p>
    ) : nfts.length === 0 ? (
      <p>No NFTs found</p>
    ) : (
      <div className="nft-grid">
        {nfts.map(nft => (
          <div key={nft.mint.toString()} className="nft-item">
            <div className="nft-info">
              <div className="nft-mint">
                {nft.mint.toString().slice(0, 6)}...{nft.mint.toString().slice(-6)}
              </div>
            </div>
          </div>
        ))}
      </div>
    )}
  </div>
);

useSolanaTransaction

Hook for tracking transaction status with confirmations.

const {
  status,  // Transaction status information
  loading, // Whether data is loading
  refetch  // Function to manually refetch status
} = useSolanaTransaction(signature);

// Example: Display transaction status
if (status) {
  return (
    <div>
      <p>Status: {status.status}</p>
      <p>Confirmations: {status.confirmations}</p>
      {status.error && <p>Error: {status.error}</p>}
    </div>
  );
}

useWalletMultiButton

Hook for building a customizable wallet connection button.

const {
  buttonState,    // State of button: connecting, connected, disconnecting, has-wallet, no-wallet
  onConnect,      // Function to connect
  onDisconnect,   // Function to disconnect
  onSelectWallet, // Function to select wallet
  walletIcon,     // Icon URL of selected wallet
  walletName,     // Name of selected wallet
  publicKey       // Public key string of connected wallet
} = useWalletMultiButton();

// Example: Custom wallet button
const renderButton = () => {
  switch (buttonState) {
    case 'connecting':
      return <button disabled>Connecting...</button>;
    case 'connected':
      return (
        <button onClick={onDisconnect}>
          {publicKey?.slice(0, 4)}...{publicKey?.slice(-4)}
        </button>
      );
    case 'disconnecting':
      return <button disabled>Disconnecting...</button>;
    case 'has-wallet':
      return <button onClick={onConnect}>Connect</button>;
    case 'no-wallet':
      return <button onClick={onSelectWallet}>Select Wallet</button>;
  }
};

useWalletModal

Hook for creating a custom wallet selection modal.

const {
  visible,           // Whether modal is visible
  showModal,         // Function to show modal
  hideModal,         // Function to hide modal
  selectedWallet,    // Currently selected wallet
  setSelectedWallet  // Function to select a wallet
} = useWalletModal();

// Example: Custom wallet modal
return (
  <>
    <button onClick={showModal}>Connect Wallet</button>
    
    {visible && (
      <div className="modal">
        <div className="modal-content">
          <h2>Select a Wallet</h2>
          {adapters.map(adapter => (
            <div 
              key={adapter.name}
              onClick={() => {
                setSelectedWallet(adapter.name);
                hideModal();
              }}
            >
              {adapter.name}
            </div>
          ))}
          <button onClick={hideModal}>Close</button>
        </div>
      </div>
    )}
  </>
);

useAnchorWallet

Hook for accessing a wallet interface that is compatible with Anchor programs.

const anchorWallet = useAnchorWallet();

// Can be passed directly to Anchor Program
if (anchorWallet) {
  const program = new Program(idl, programId, {
    connection,
    wallet: anchorWallet
  });
}

useLocalStorage

Hook for accessing and updating persistent storage.

const [value, setValue, loading] = useLocalStorage('my-key', 'default-value');

๐Ÿ”„ Advanced Usage

Sending Transactions

import { useWallet, useConnection } from '@hermis/solana-headless-react';
import { Transaction, SystemProgram, LAMPORTS_PER_SOL, PublicKey } from '@hermis/solana-headless-core';

function SendTransaction() {
  const { publicKey, sendTransaction } = useWallet();
  const { connection } = useConnection();
  
  const handleSend = async () => {
    if (!publicKey) return;
    
    // Create a new transaction to send 0.1 SOL to recipient
    const transaction = new Transaction();
    const recipientPubKey = new PublicKey('recipient-address');
    
    transaction.add(
      SystemProgram.transfer({
        fromPubkey: publicKey,
        toPubkey: recipientPubKey,
        lamports: 0.1 * LAMPORTS_PER_SOL
      })
    );
    
    try {
      // Get recent blockhash
      const { blockhash } = await connection.getLatestBlockhash();
      transaction.recentBlockhash = blockhash;
      transaction.feePayer = publicKey;
      
      // Send transaction
      const signature = await sendTransaction(transaction, connection);
      console.log('Transaction sent:', signature);
    } catch (error) {
      console.error('Error sending transaction:', error);
    }
  };
  
  return (
    <button onClick={handleSend} disabled={!publicKey}>
      Send 0.1 SOL
    </button>
  );
}

Custom Storage Persistence

You can customize how wallet selection is persisted across sessions:

import { HermisProvider, createIndexedDBStorageFactory } from '@hermis/solana-headless-react';

// Create IndexedDB storage factory
const indexedDBStorage = createIndexedDBStorageFactory('solana-wallets', 'wallet-store');

function App() {
  return (
    <HermisProvider
      rpcEndpoint="https://api.mainnet-beta.solana.com"
      storageFactory={indexedDBStorage}
      // ...other props
    >
      {children}
    </HermisProvider>
  );
}

Error Handling

Handle wallet connection errors gracefully:

<HermisProvider
  rpcEndpoint="https://api.devnet.solana.com"
  onError={(error, adapter) => {
    console.error(`Error with ${adapter?.name || 'wallet'}:`, error);
    // Show user-friendly error notification
    toast.error(`Wallet error: ${error.message}`);
  }}
>
  {children}
</HermisProvider>

๐Ÿ“‹ Tips & Best Practices

  1. Handle Connection States: Always check connecting, connected, and disconnecting states to provide appropriate UI feedback.

  2. Error Handling: Implement proper error handling for wallet operations, especially during transactions.

  3. Mobile Support: Test your application on mobile devices and ensure you're handling mobile wallet redirects correctly.

  4. Security: Never request or store a user's private keys or seed phrases.

  5. Network Selection: Make sure your RPC endpoint matches the network you intend to use (e.g., Mainnet, Devnet).

  6. React StrictMode: The wallet hooks work correctly in StrictMode, which may render components twice during development.

  7. Performance: For high-frequency data like balances, consider using larger refresh intervals in production.

๐Ÿ“„ License

This project is licensed under the Apache 2.0 License - see the LICENSE file for details.

1.0.8

10 months ago

1.0.7

10 months ago

1.0.6

10 months ago

1.0.5

10 months ago

1.0.4

10 months ago

1.0.3

10 months ago

1.0.2

10 months ago

1.0.1

11 months ago

1.0.0

11 months ago