Skip to main content

Overview

The @hermis/solana-headless-adapter-base package provides a framework-agnostic wallet adapter implementation. It’s perfect for vanilla JavaScript applications or when you need direct control over wallet adapter management.

Installation

npm install @hermis/solana-headless-adapter-base @solana/wallet-adapter-phantom @solana/wallet-adapter-solflare

Step 1: Initialize Wallet Adapters

Set up wallet adapters for your application:
import {
  WalletAdapterManager,
  getStandardWalletAdapters,
  sortWalletAdapters
} from '@hermis/solana-headless-adapter-base';

// Create wallet adapter instances
const wallets = [
  /** Other wallet that implement the wallet standard */
];

// Get all standard wallet adapters (includes Wallet Standard wallets)
const adapters = getStandardWalletAdapters(wallets);

console.log(`Found ${adapters.length} wallet adapters`);
The getStandardWalletAdapters function automatically detects Wallet Standard compatible wallets in addition to the adapters you provide.

Step 2: Create Wallet Manager

Initialize the WalletAdapterManager to manage wallet connections:
import { WalletAdapterManager } from '@hermis/solana-headless-adapter-base';

// Create the wallet manager
const walletManager = new WalletAdapterManager(adapters);

// Subscribe to wallet events
walletManager.on('connect', (publicKey) => {
  console.log('Connected to wallet:', publicKey.toBase58());
  updateUI('connected', publicKey.toBase58());
});

walletManager.on('disconnect', () => {
  console.log('Wallet disconnected');
  updateUI('disconnected');
});

walletManager.on('error', (error) => {
  console.error('Wallet error:', error);
  showError(error.message);
});

walletManager.on('walletChanged', (wallet) => {
  console.log('Wallet changed to:', wallet?.adapter.name);
});

console.log('Wallet manager initialized');

Step 3: Connect to a Wallet

Select and connect to a wallet:
// Find a specific wallet adapter
const phantomAdapter = adapters.find(adapter => adapter.name === 'Phantom');

if (phantomAdapter) {
  // Select the wallet
  walletManager.selectAdapter(phantomAdapter.name);

  // Connect to the wallet
  async function connectWallet() {
    try {
      const adapter = await walletManager.connect();
      if (adapter) {
        console.log('Successfully connected to', adapter.name);
        console.log('Public key:', adapter.publicKey?.toBase58());
      }
    } catch (error) {
      console.error('Connection error:', error);
      alert('Failed to connect to wallet');
    }
  }

  // Call the connect function
  connectWallet();
}

Step 4: Build a Wallet Selector UI

Create a UI for users to select from available wallets:
// Sort adapters by priority (mobile wallets, then installed wallets first)
const sortedAdapters = sortWalletAdapters(adapters);

// Create wallet selection UI
function createWalletSelector() {
  const container = document.getElementById('wallet-selector');

  sortedAdapters.forEach(adapter => {
    const button = document.createElement('button');
    button.textContent = adapter.name;
    button.className = 'wallet-option';

    // Add visual indicator for installed wallets
    if (adapter.readyState === 'Installed') {
      button.classList.add('installed');
      const badge = document.createElement('span');
      badge.textContent = '✓ Installed';
      badge.className = 'badge';
      button.appendChild(badge);
    }

    button.onclick = async () => {
      walletManager.selectAdapter(adapter.name);
      try {
        await walletManager.connect();
      } catch (error) {
        console.error('Connection failed:', error);
      }
    };

    container.appendChild(button);
  });
}

// Initialize the selector
createWalletSelector();

Step 5: Filter Wallets by Ready State

Filter adapters based on their installation status:
import { getAdaptersByReadyState } from '@hermis/solana-headless-adapter-base';

// Get only installed wallets
const installed = getAdaptersByReadyState(adapters, 'Installed');
console.log('Installed wallets:', installed.map(a => a.name));

// Get loadable wallets (can be loaded on demand)
const loadable = getAdaptersByReadyState(adapters, 'Loadable');
console.log('Loadable wallets:', loadable.map(a => a.name));

// Get not detected wallets
const notDetected = getAdaptersByReadyState(adapters, 'NotDetected');
console.log('Not detected wallets:', notDetected.map(a => a.name));

// Get all ready wallets (Installed + Loadable)
const ready = [
  ...getAdaptersByReadyState(adapters, 'Installed'),
  ...getAdaptersByReadyState(adapters, 'Loadable')
];

Complete Example

Here’s a complete vanilla JavaScript example:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Solana Wallet Demo</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      max-width: 800px;
      margin: 50px auto;
      padding: 20px;
    }
    .wallet-option {
      display: block;
      width: 100%;
      padding: 15px;
      margin: 10px 0;
      border: 2px solid #ddd;
      background: white;
      cursor: pointer;
      border-radius: 8px;
      font-size: 16px;
    }
    .wallet-option.installed {
      border-color: #14F195;
      background: #f0fdf4;
    }
    .wallet-info {
      background: #f5f5f5;
      padding: 20px;
      border-radius: 8px;
      margin-top: 20px;
    }
    .badge {
      display: inline-block;
      margin-left: 10px;
      font-size: 12px;
      color: #14F195;
    }
  </style>
</head>
<body>
  <h1>Solana Wallet Integration</h1>
  <div id="wallet-selector"></div>
  <div id="wallet-info" style="display: none;" class="wallet-info">
    <h3>Connected Wallet</h3>
    <p><strong>Name:</strong> <span id="wallet-name"></span></p>
    <p><strong>Address:</strong> <span id="wallet-address"></span></p>
    <button id="disconnect-btn">Disconnect</button>
  </div>

  <script type="module" src="app.js"></script>
</body>
</html>
app.js
import {
  WalletAdapterManager,
  getStandardWalletAdapters,
  sortWalletAdapters,
} from '@hermis/solana-headless-adapter-base';

class WalletApp {
  constructor() {
    this.manager = null;
    this.init();
  }

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

    const adapters = getStandardWalletAdapters(wallets);
    const sorted = sortWalletAdapters(adapters);

    // Create wallet manager
    this.manager = new WalletAdapterManager(adapters);

    // Setup event listeners
    this.setupEventListeners();

    // Render wallet options
    this.renderWalletOptions(sorted);
  }

  setupEventListeners() {
    this.manager.on('connect', (publicKey) => {
      console.log('Connected:', publicKey.toBase58());
      this.showWalletInfo(publicKey.toBase58());
    });

    this.manager.on('disconnect', () => {
      console.log('Disconnected');
      this.hideWalletInfo();
    });

    this.manager.on('error', (error) => {
      console.error('Error:', error);
      alert(`Wallet error: ${error.message}`);
    });

    // Disconnect button
    document.getElementById('disconnect-btn').onclick = () => {
      this.manager.disconnect();
    };
  }

  renderWalletOptions(adapters) {
    const container = document.getElementById('wallet-selector');

    adapters.forEach(adapter => {
      const button = document.createElement('button');
      button.className = 'wallet-option';
      button.textContent = adapter.name;

      if (adapter.readyState === 'Installed') {
        button.classList.add('installed');
        const badge = document.createElement('span');
        badge.textContent = '✓ Installed';
        badge.className = 'badge';
        button.appendChild(badge);
      }

      button.onclick = async () => {
        this.manager.selectAdapter(adapter.name);
        try {
          await this.manager.connect();
        } catch (error) {
          console.error('Connection failed:', error);
        }
      };

      container.appendChild(button);
    });
  }

  showWalletInfo(address) {
    const walletInfo = document.getElementById('wallet-info');
    const walletName = document.getElementById('wallet-name');
    const walletAddress = document.getElementById('wallet-address');

    walletName.textContent = this.manager.wallet?.adapter.name || 'Unknown';
    walletAddress.textContent = address;
    walletInfo.style.display = 'block';

    // Hide selector
    document.getElementById('wallet-selector').style.display = 'none';
  }

  hideWalletInfo() {
    document.getElementById('wallet-info').style.display = 'none';
    document.getElementById('wallet-selector').style.display = 'block';
  }
}

// Initialize the app
const app = new WalletApp();

API Reference

WalletAdapterManager

constructor
function
Creates a new wallet adapter manager
new WalletAdapterManager(adapters: Adapter[])
selectAdapter
function
Selects a wallet adapter by name
selectAdapter(walletName: string): void
connect
function
Connects to the selected wallet
connect(): Promise<Adapter | null>
disconnect
function
Disconnects from the current wallet
disconnect(): Promise<void>
on
function
Subscribe to wallet events
on(event: string, callback: Function): void
// Events: 'connect', 'disconnect', 'error', 'walletChanged'

Utility Functions

getStandardWalletAdapters
function
Gets all standard wallet adapters including custom ones
getStandardWalletAdapters(customAdapters?: Adapter[]): Adapter[]
sortWalletAdapters
function
Sorts adapters by priority (mobile wallets, then installed wallets first)
sortWalletAdapters(adapters: Adapter[]): Adapter[]
getAdaptersByReadyState
function
Filters adapters by ready state
getAdaptersByReadyState(
  adapters: Adapter[],
  readyState: 'Installed' | 'Loadable' | 'NotDetected' | 'Unsupported'
): Adapter[]

Best Practices

Use events to update your UI reactively:
manager.on('connect', (publicKey) => {
  updateUI({ connected: true, address: publicKey.toBase58() });
});
Always handle connection errors gracefully:
import { HermisError, isUserRejection } from '@hermis/errors';

try {
  await manager.connect();
} catch (error) {
  if (error instanceof HermisError) {
    // Handle Hermis-specific errors
    console.error('Hermis Error:', error.code, error.message);
  } else if (isUserRejection(error)) {
    // User rejected the connection (handles error code 4001 internally)
    console.log('User rejected connection');
  } else {
    console.error('Connection failed:', error);
  }
}
Prioritize installed wallets in your UI:
const sorted = sortWalletAdapters(adapters);
// Installed wallets appear first
Clean up event listeners when done:
manager.removeListener('connect', handleConnect);
// or disconnect when component unmounts
await manager.disconnect();

What’s Next?