Overview
This example demonstrates how to use Hermis with vanilla JavaScript - no framework required! The guide shows examples for both web3.js (legacy) and @solana/kit (modern) architectures.Architecture Guide
Web3.js (Legacy)
- Mature and widely supported
- Compatible with all existing Solana tools
- Well-documented ecosystem
- Larger bundle size
@solana/kit (Modern)
- Type-safe instruction building
- Better tree-shaking for smaller bundles
- Modern API design
- Future-proof architecture
WalletAdapterManager! You can even use Kit signers with wallet adapters through createKitSignersFromAdapter.
Project Setup
Copy
mkdir solana-wallet-app
cd solana-wallet-app
npm init -y
# For web3.js architecture
npm install @hermis/solana-headless-adapter-base @hermis/errors
npm install @solana/wallet-adapter-phantom @solana/wallet-adapter-solflare @solana/web3.js
# For Kit architecture (optional)
npm install @solana/web-sdk @solana-program/system @solana/kit
HTML Structure
index.html
Copy
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Solana Wallet App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<h1>Solana Wallet Integration</h1>
<!-- Wallet Selection -->
<div id="wallet-selector" class="wallet-selector"></div>
<!-- Wallet Info (hidden by default) -->
<div id="wallet-info" class="wallet-info" style="display: none;">
<h2>Connected Wallet</h2>
<p><strong>Wallet:</strong> <span id="wallet-name"></span></p>
<p><strong>Address:</strong> <span id="wallet-address"></span></p>
<p><strong>Balance:</strong> <span id="wallet-balance">Loading...</span> SOL</p>
<button id="disconnect-btn" class="button button-secondary">Disconnect</button>
</div>
</div>
<script type="module" src="app.js"></script>
</body>
</html>
Styling
style.css
Copy
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.container {
background: white;
padding: 40px;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
max-width: 600px;
width: 100%;
}
h1 {
color: #333;
margin-bottom: 30px;
text-align: center;
}
.wallet-selector {
display: flex;
flex-direction: column;
gap: 10px;
}
.wallet-option {
display: flex;
align-items: center;
padding: 15px 20px;
border: 2px solid #e0e0e0;
border-radius: 10px;
background: white;
cursor: pointer;
transition: all 0.3s;
}
.wallet-option:hover {
border-color: #667eea;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.2);
}
.wallet-option.installed {
border-color: #14F195;
background: #f0fdf4;
}
.wallet-info {
text-align: center;
}
.wallet-info p {
margin: 15px 0;
color: #666;
}
.wallet-info strong {
color: #333;
}
.button {
padding: 12px 24px;
border: none;
border-radius: 8px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s;
margin-top: 20px;
}
.button-secondary {
background: #ff4757;
color: white;
}
.button-secondary:hover {
background: #ff3838;
}
#wallet-address {
font-family: 'Courier New', monospace;
font-size: 14px;
}
Wallet Connection
Copy
import { WalletAdapterManager, getStandardWalletAdapters } from '@hermis/solana-headless-adapter-base';
import { Connection, clusterApiUrl, LAMPORTS_PER_SOL } from '@solana/web3.js';
// Global state
let manager;
let connection;
// Initialize wallet manager and connection
function initWalletManager() {
connection = new Connection(clusterApiUrl('devnet'));
// Auto-detect wallet-standard compatible wallets
const wallets = []; // Add custom wallet adapters here if needed
const adapters = getStandardWalletAdapters(wallets);
manager = new WalletAdapterManager(adapters);
setupEventListeners();
renderWalletOptions();
}
// Setup wallet event listeners
function setupEventListeners() {
manager.on('connect', async (publicKey) => {
console.log('Connected:', publicKey.toBase58());
showWalletInfo(publicKey.toBase58());
await updateBalance(publicKey);
});
manager.on('disconnect', () => {
console.log('Disconnected');
hideWalletInfo();
});
manager.on('error', (error) => {
console.error('Error:', error);
alert(`Wallet error: ${error.message}`);
});
document.getElementById('disconnect-btn').onclick = () => {
manager.disconnect();
};
}
// Render available wallet options
function renderWalletOptions() {
const container = document.getElementById('wallet-selector');
const adapters = manager.getAdapters();
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.style.color = '#14F195';
badge.style.marginLeft = '10px';
button.appendChild(badge);
}
button.onclick = async () => {
manager.selectAdapter(adapter.name);
try {
await manager.connect();
} catch (error) {
console.error('Connection failed:', error);
}
};
container.appendChild(button);
});
}
// Update wallet balance
async function updateBalance(publicKey) {
try {
const balance = await connection.getBalance(publicKey);
const solBalance = balance / LAMPORTS_PER_SOL;
document.getElementById('wallet-balance').textContent = solBalance.toFixed(4);
} catch (error) {
console.error('Failed to get balance:', error);
document.getElementById('wallet-balance').textContent = 'Error';
}
}
// Show wallet info UI
function showWalletInfo(address) {
document.getElementById('wallet-name').textContent = manager.getSelectedAdapter()?.name || 'Unknown';
document.getElementById('wallet-address').textContent = address;
document.getElementById('wallet-info').style.display = 'block';
document.getElementById('wallet-selector').style.display = 'none';
}
// Hide wallet info UI
function hideWalletInfo() {
document.getElementById('wallet-info').style.display = 'none';
document.getElementById('wallet-selector').style.display = 'flex';
}
// Initialize on load
initWalletManager();
Send Transaction
Copy
import { Transaction, SystemProgram, PublicKey, LAMPORTS_PER_SOL } from '@solana/web3.js';
// Send SOL transaction
async function sendSOL(recipientAddress, amountInSOL) {
const publicKey = manager.getAdapter()?.publicKey;
if (!publicKey) {
throw new Error('Wallet not connected');
}
try {
const transaction = new Transaction();
transaction.add(
SystemProgram.transfer({
fromPubkey: publicKey,
toPubkey: new PublicKey(recipientAddress),
lamports: amountInSOL * LAMPORTS_PER_SOL
})
);
const { blockhash } = await connection.getLatestBlockhash();
transaction.recentBlockhash = blockhash;
transaction.feePayer = publicKey;
const signature = await manager.signAndSendTransaction(connection, transaction);
if (!signature) {
throw new Error('Transaction failed');
}
console.log('✅ Transaction signature:', signature);
// Wait for confirmation
await connection.confirmTransaction(signature, 'confirmed');
console.log('✅ Transaction confirmed!');
return signature;
} catch (error) {
console.error('❌ Transaction error:', error);
throw error;
}
}
// Wire up send button
document.getElementById('send-btn').onclick = async () => {
const recipient = document.getElementById('recipient').value;
const amount = parseFloat(document.getElementById('amount').value);
const statusEl = document.getElementById('tx-status');
if (!recipient || !amount) {
statusEl.textContent = 'Please enter recipient and amount';
return;
}
try {
statusEl.textContent = 'Sending transaction...';
const signature = await sendSOL(recipient, amount);
statusEl.textContent = `Success! Signature: ${signature.substring(0, 20)}...`;
} catch (error) {
statusEl.textContent = `Error: ${error.message}`;
}
};
Copy
<!-- Add to wallet-info div -->
<div class="transaction-form">
<h3>Send SOL</h3>
<input type="text" id="recipient" placeholder="Recipient Address" />
<input type="number" id="amount" placeholder="Amount (SOL)" step="0.01" min="0" />
<button id="send-btn" class="button button-primary">Send Transaction</button>
<p id="tx-status"></p>
</div>
Sign Message
Copy
// Sign message with wallet
async function signMessage(message) {
const publicKey = manager.getAdapter()?.publicKey;
if (!publicKey) {
throw new Error('Wallet not connected');
}
try {
// Encode message to Uint8Array
const encodedMessage = new TextEncoder().encode(message);
// Sign using manager
const signature = await manager.signMessage(encodedMessage);
if (!signature) {
throw new Error('Message signing failed');
}
console.log('✅ Signature:', signature);
return signature;
} catch (error) {
console.error('❌ Signing error:', error);
throw error;
}
}
// Wire up sign button
document.getElementById('sign-btn').onclick = async () => {
const message = document.getElementById('message-input').value;
const outputEl = document.getElementById('signature-output');
if (!message) {
outputEl.textContent = 'Please enter a message';
return;
}
try {
outputEl.textContent = 'Signing message...';
const signature = await signMessage(message);
outputEl.textContent = `Signature: ${Array.from(signature).slice(0, 10).join(', ')}...`;
} catch (error) {
outputEl.textContent = `Error: ${error.message}`;
}
};
Copy
<!-- Add to wallet-info div -->
<div class="message-form">
<h3>Sign Message</h3>
<textarea id="message-input" placeholder="Enter message to sign"></textarea>
<button id="sign-btn" class="button button-primary">Sign Message</button>
<p id="signature-output"></p>
</div>
Build Setup
package.json
Copy
{
"name": "solana-wallet-app",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build"
},
"devDependencies": {
"vite": "^5.0.0"
}
}
Running the Example
Copy
npm run dev
Features Demonstrated
Web3.js Architecture
Wallet connection with WalletAdapterManager
SOL balance fetching
Transaction sending (SOL transfers)
Message signing
Event-driven wallet state
Error handling
Kit Architecture
Kit signer creation from adapters
Kit transaction building with pipe pattern
Kit message signing with signBytes
Type-safe instruction building
Functional composition with pipe
Modern RPC interface
Both Architectures
Same WalletAdapterManager for connection
Seamless bridge via createKitSignersFromAdapter
Multi-wallet support
Event-driven architecture
