Skip to main content

Overview

This guide will walk you through integrating Hermis into a React application. You’ll learn how to set up wallet connections, display wallet information, and perform basic Solana operations.

Installation

Install the React package and its peer dependencies:
npm install @hermis/solana-headless-react @solana/wallet-adapter-phantom @solana/wallet-adapter-solflare

Step 1: Set Up the Provider

Wrap your app with the HermisProvider to enable wallet functionality throughout your application:
App.tsx
import React from 'react';
import { HermisProvider } from '@hermis/solana-headless-react';
import { WalletAdapterNetwork } from '@solana/wallet-adapter-base';
import Home from './Home';

function App() {
  // Auto-detect wallet-standard compatible wallets
  const wallets = []; // Add custom wallet adapters here if needed

  return (
    <HermisProvider
      endpoint="https://api.devnet.solana.com"
      network={WalletAdapterNetwork.Devnet}
      autoConnect={true}
      wallets={wallets}
      onError={(error) => console.error('Wallet error:', error)}
    >
      <Home />
    </HermisProvider>
  );
}

export default App;

Provider Options

endpoint
string
required
The Solana RPC endpoint URL (e.g., https://api.devnet.solana.com)
network
WalletAdapterNetwork
required
The Solana network: Mainnet, Devnet, or Testnet
autoConnect
boolean
default:"false"
Automatically reconnect to the last used wallet on page load
wallets
Adapter[]
Array of wallet adapters to support
onError
(error: Error) => void
Callback function for handling wallet errors

Step 2: Create a Wallet Component

Use the useWallet hook to access wallet functionality:
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 className="container">
      <h1>Solana Wallet Demo</h1>

      {!connected ? (
        <div>
          <button
            onClick={handleConnect}
            disabled={connecting}
            className="connect-button"
          >
            {connecting ? 'Connecting...' : 'Connect Wallet'}
          </button>
        </div>
      ) : (
        <div className="wallet-info">
          <p><strong>Wallet:</strong> {wallet?.adapter.name}</p>
          <p><strong>Address:</strong> {publicKey?.toBase58()}</p>
          <p><strong>Balance:</strong> {loading ? 'Loading...' : `${balance} SOL`}</p>
          <button onClick={disconnect} className="disconnect-button">
            Disconnect
          </button>
        </div>
      )}
    </div>
  );
}

export default Home;

Step 3: Send a Transaction

Add transaction functionality using the sendTransaction method:
import React, { useState } from 'react';
import { useWallet, useConnection } from '@hermis/solana-headless-react';
import { Transaction, SystemProgram, LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js';

function SendTransaction() {
  const { publicKey, sendTransaction } = useWallet();
  const { connection } = useConnection();
  const [recipient, setRecipient] = useState('');
  const [amount, setAmount] = useState('0.1');
  const [sending, setSending] = useState(false);
  const [signature, setSignature] = useState('');

  const handleSend = async () => {
    if (!publicKey || !recipient) return;

    setSending(true);
    setSignature('');

    try {
      const transaction = new Transaction();
      const recipientPubKey = new PublicKey(recipient);

      transaction.add(
        SystemProgram.transfer({
          fromPubkey: publicKey,
          toPubkey: recipientPubKey,
          lamports: parseFloat(amount) * LAMPORTS_PER_SOL
        })
      );

      // Get recent blockhash
      const { blockhash } = await connection.getLatestBlockhash();
      transaction.recentBlockhash = blockhash;
      transaction.feePayer = publicKey;

      // Send transaction
      const sig = await sendTransaction(transaction, connection);
      setSignature(sig);
      console.log('Transaction sent:', sig);
    } catch (error) {
      console.error('Error sending transaction:', error);
      alert('Failed to send transaction');
    } finally {
      setSending(false);
    }
  };

  return (
    <div className="send-transaction">
      <h2>Send SOL</h2>
      <div className="form">
        <input
          type="text"
          placeholder="Recipient address"
          value={recipient}
          onChange={(e) => setRecipient(e.target.value)}
          className="input"
        />
        <input
          type="number"
          placeholder="Amount (SOL)"
          value={amount}
          onChange={(e) => setAmount(e.target.value)}
          step="0.01"
          min="0"
          className="input"
        />
        <button
          onClick={handleSend}
          disabled={!publicKey || !recipient || sending}
          className="send-button"
        >
          {sending ? 'Sending...' : 'Send SOL'}
        </button>
      </div>
      {signature && (
        <div className="success">
          <p>Transaction successful!</p>
          <a
            href={`https://explorer.solana.com/tx/${signature}?cluster=devnet`}
            target="_blank"
            rel="noopener noreferrer"
          >
            View on Explorer
          </a>
        </div>
      )}
    </div>
  );
}

export default SendTransaction;

Available Hooks

useWallet

Primary hook for wallet interactions:
const {
  // Wallet state
  autoConnect,           // Whether auto-connect is enabled
  wallets,               // Array of all available wallets
  wallet,                // Currently selected wallet
  publicKey,             // Public key of connected wallet (web3.js)
  connecting,            // Connection status
  connected,             // Whether wallet is connected
  disconnecting,         // Disconnection status

  // Kit-specific properties
  address,               // Kit Address type (null if not connected)
  addressString,         // Plain address string (null if not connected)
  chain,                 // Solana chain identifier (e.g., 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1')
  messageSigner,         // Message signer for Kit architecture (null if not available)
  transactionSigner,     // Transaction sending signer for Kit (null if not available)
  getChainId,            // Function to get chain ID: (network: 'devnet' | 'mainnet' | 'testnet') => `solana:${string}`

  // Methods
  select,                // Select a wallet by name
  connect,               // Connect to selected wallet
  disconnect,            // Disconnect wallet

  // Transaction methods (support both web3.js and Kit)
  sendTransaction,       // Send a transaction (dual architecture support)
  signTransaction,       // Sign a transaction (dual architecture support)
  signAllTransactions,   // Sign multiple transactions (dual architecture support)
  signAndSendTransaction, // Sign and send transaction (dual architecture support)

  // Message signing
  signMessage,           // Sign a message
  signIn,                // Sign in with wallet

  // Utilities
  hasFeature,            // Check if wallet supports a feature: (feature: 'signMessage' | 'signTransaction' | 'signAllTransactions' | 'signIn') => boolean
} = useWallet();

useConnection

Access the Solana connection:
const {
  connection,  // DualConnection - supports both web3.js Connection and Kit Rpc
  network      // WalletAdapterNetwork (optional)
} = useConnection();

// Works with both architectures
const accountInfo = await connection.getAccountInfo(publicKey);

// For Kit RPC, use the .send() pattern
const { value } = await connection.getLatestBlockhash().send();

useSolanaBalance

Fetch and track wallet balance:
const {
  balance,         // Balance in SOL
  balanceLamports, // Balance in lamports
  loading,         // Loading state
  error,           // Error if occurred
  refetch          // Manually refetch balance
} = useSolanaBalance(publicKey, 10000); // 10 second refresh

useSolanaTokenAccounts

Fetch SPL token accounts:
const {
  tokenAccounts, // Array of token accounts
  loading,       // Loading state
  error,         // Error if occurred
  refetch        // Manually refetch
} = useSolanaTokenAccounts(publicKey);

useSolanaNFTs

Fetch NFTs owned by a wallet:
const {
  nfts,    // Array of NFTs
  loading, // Loading state
  error,   // Error if occurred
  refetch  // Manually refetch
} = useSolanaNFTs(publicKey);

useSolanaTransaction

Track transaction status with confirmations:
const {
  status,  // TransactionStatus object
  loading, // Loading state
  refetch  // Manually refetch status
} = useSolanaTransaction(signature, autoFetch);

// TransactionStatus includes:
// - signature: string
// - status: 'pending' | 'confirmed' | 'finalized' | 'failed'
// - confirmations: number
// - confirmationStatus: SignatureStatus['confirmationStatus']
// - error?: string
// - transactionDetails?: VersionedTransactionResponse

useWalletAdapters

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

useAnchorWallet

Get an Anchor-compatible wallet with dual architecture support:
const anchorWallet = useAnchorWallet();
// Returns AnchorWallet | undefined

// Primary use: Pass to Anchor programs (uses web3.js internally)
const program = new Program(idl, programId, { connection, wallet: anchorWallet });
await program.methods.myMethod().accounts({...}).rpc();

// Advanced use: Manual signing with web3.js
const signedTx = await anchorWallet.signTransaction(web3Transaction);

// Advanced use: Manual signing with Kit (also supported)
const signedKitTx = await anchorWallet.signTransaction(kitTransactionMessage);

useWalletModal

Manage wallet selection modal state:
const {
  visible,           // Whether modal is visible
  showModal,         // Show the modal
  hideModal,         // Hide the modal
  selectedWallet,    // Currently selected wallet name
  setSelectedWallet  // Set selected wallet
} = useWalletModal();

Next.js Integration

For Next.js 13+ with App Router, create a client-side provider:
app/providers.tsx
'use client';

import { HermisProvider } from '@hermis/solana-headless-react';
import { WalletAdapterNetwork } from '@solana/wallet-adapter-base';

export function Providers({ children }: { children: React.ReactNode }) {
  // Auto-detect wallet-standard compatible wallets
  const wallets = []; // Add custom wallet adapters here if needed

  return (
    <HermisProvider
      endpoint="https://api.devnet.solana.com"
      network={WalletAdapterNetwork.Devnet}
      autoConnect={true}
      wallets={wallets}
    >
      {children}
    </HermisProvider>
  );
}
Then use in your 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>
  );
}

Best Practices

Always check connecting, connected, and disconnecting states to provide appropriate UI feedback to users.
Implement proper error handling for all wallet operations, especially during transactions. Use the onError callback in the provider.
For best performance with Solana Mobile Wallet Adapter, disable battery/power saving mode on your device. Power saving modes can interfere with wallet adapter connections, transaction signing, and deeplinking to wallet applications.Disabling power saving mode ensures:
  • Reliable wallet adapter connections
  • Smooth deeplinking between your dApp and wallet apps
  • Faster transaction signing responses
Tip: Instruct users to disable power saving mode before connecting their mobile wallet for optimal experience.
Ensure your RPC endpoint matches the intended network. Don’t mix mainnet and devnet operations.

What’s Next?