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:
<! 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 : 800 px ;
margin : 50 px auto ;
padding : 20 px ;
}
.wallet-option {
display : block ;
width : 100 % ;
padding : 15 px ;
margin : 10 px 0 ;
border : 2 px solid #ddd ;
background : white ;
cursor : pointer ;
border-radius : 8 px ;
font-size : 16 px ;
}
.wallet-option.installed {
border-color : #14F195 ;
background : #f0fdf4 ;
}
.wallet-info {
background : #f5f5f5 ;
padding : 20 px ;
border-radius : 8 px ;
margin-top : 20 px ;
}
.badge {
display : inline-block ;
margin-left : 10 px ;
font-size : 12 px ;
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 >
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
Creates a new wallet adapter manager new WalletAdapterManager ( adapters : Adapter [])
Selects a wallet adapter by name selectAdapter ( walletName : string ): void
Connects to the selected wallet connect (): Promise < Adapter | null >
Disconnects from the current wallet disconnect (): Promise < void >
Subscribe to wallet events on ( event : string , callback : Function ): void
// Events: 'connect', 'disconnect', 'error', 'walletChanged'
Utility Functions
getStandardWalletAdapters
Gets all standard wallet adapters including custom ones getStandardWalletAdapters ( customAdapters ?: Adapter []): Adapter []
Sorts adapters by priority (mobile wallets, then installed wallets first) sortWalletAdapters ( adapters : Adapter []): Adapter []
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?