Documentation Index
Fetch the complete documentation index at: https://docs.hermis.dev/llms.txt
Use this file to discover all available pages before exploring further.
Basic Message Signing
import { useWallet } from '@hermis/solana-headless-react';
function SignMessage() {
const { signMessage, publicKey } = useWallet();
const handleSign = async () => {
const message = `Sign this message to authenticate.\nTimestamp: ${Date.now()}`;
const encodedMessage = new TextEncoder().encode(message);
const signature = await signMessage(encodedMessage);
console.log('Signature:', signature);
return { message, signature, publicKey: publicKey?.toBase58() };
};
return <button onClick={handleSign}>Sign Message</button>;
}
Authentication Flow
function AuthComponent() {
const { signMessage, publicKey } = useWallet();
const [isAuthenticated, setIsAuthenticated] = useState(false);
const authenticate = async () => {
try {
const nonce = crypto.randomUUID();
const message = `Sign in to MyDApp\nNonce: ${nonce}`;
const encodedMessage = new TextEncoder().encode(message);
const signature = await signMessage(encodedMessage);
// Send to backend for verification
const response = await fetch('/api/auth/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
publicKey: publicKey?.toBase58(),
message,
signature: Array.from(signature),
}),
});
if (response.ok) {
const { token } = await response.json();
localStorage.setItem('auth-token', token);
setIsAuthenticated(true);
}
} catch (error) {
console.error('Authentication failed:', error);
}
};
return (
<div>
{isAuthenticated ? (
<p>Authenticated!</p>
) : (
<button onClick={authenticate}>Sign In</button>
)}
</div>
);
}
Backend Verification (Conceptual)
On your backend, verify message signatures by following these steps:
- Receive data from client: message, signature, and public key
- Verify Ed25519 signature: Use a cryptographic library (e.g.,
tweetnacl, @noble/ed25519)
- Validate message contents: Check nonce, timestamp, and domain match expectations
- Prevent replay attacks: Store used nonces temporarily and reject duplicates
- Check expiration: Verify message timestamp is recent (e.g., within 5 minutes)
- Generate auth token: Create JWT or session token if signature is valid
Security Notes:
- Never trust client-side verification - always verify server-side
- Use HTTPS to protect signatures in transit
- Rate limit verification endpoints to prevent brute force attacks
- Store nonces with TTL to prevent replay attacks
Pseudo-code for verification:
// 1. Parse and validate input
message_bytes = utf8_encode(message)
signature_bytes = decode_signature(signature)
public_key_bytes = decode_public_key(public_key)
// 2. Verify signature
if !verify_ed25519(message_bytes, signature_bytes, public_key_bytes):
return error("Invalid signature")
// 3. Check nonce not used
if nonce_exists_in_cache(nonce):
return error("Nonce already used")
cache_nonce(nonce, ttl=300) // 5 minutes
// 4. Check message not expired
if timestamp < now() - 300: // 5 minutes
return error("Message expired")
// 5. Generate token
token = generate_jwt(public_key)
return success(token)
Sign-In With Solana (SIWS)
function SignInWithSolana() {
const { signIn } = useWallet();
const handleSignIn = async () => {
const { account, signedMessage, signature } = await signIn({
domain: window.location.host,
statement: 'Sign in to MyDApp',
uri: window.location.origin,
});
// Verify on backend
const response = await fetch('/api/auth/siws', {
method: 'POST',
body: JSON.stringify({ account, signedMessage, signature }),
});
const { token } = await response.json();
localStorage.setItem('token', token);
};
return <button onClick={handleSignIn}>Sign In</button>;
}
Message Signing (@solana/kit)
import { useWallet, useConnection } from '@hermis/solana-headless-react';
import { createKitSignersFromAdapter } from '@hermis/solana-headless-adapter-base';
import { HermisError, HERMIS_ERROR__WALLET_INTERACTION__FEATURE_NOT_SUPPORTED } from '@hermis/errors';
import { signBytes } from '@solana/kit';
function SignMessageKit() {
const { wallet, publicKey } = useWallet();
const { connection } = useConnection();
const handleSign = async () => {
if (!wallet || !publicKey) return;
// Create Kit signers from adapter
const { messageSigner } = createKitSignersFromAdapter(
wallet.adapter,
connection
);
if (!messageSigner) {
throw new HermisError(
HERMIS_ERROR__WALLET_INTERACTION__FEATURE_NOT_SUPPORTED,
{ feature: 'message signing' }
);
}
const message = `Sign this message to authenticate.\nTimestamp: ${Date.now()}`;
const messageBytes = new TextEncoder().encode(message);
// Sign using Kit message signer
const signatures = await signBytes(messageSigner, messageBytes);
const signature = signatures[messageSigner.address];
console.log('Signature:', signature);
return {
message,
signature: Array.from(signature),
publicKey: publicKey.toBase58(),
};
};
return <button onClick={handleSign}>Sign Message (Kit)</button>;
}
When to use Kit for message signing:
- Building with @solana/kit architecture
- Need type-safe signer interfaces
- Working with Kit-native libraries
- Want better tree-shaking and smaller bundles
Note: The Kit messageSigner integrates seamlessly with wallet adapters through createKitSignersFromAdapter, providing a bridge between traditional wallet adapters and modern Kit signers.