Client Setup
Set up the SIWP client plugin for wallet authentication.
Installation
npm i @zig-zag/better-siwp better-auth @talismn/siwsYou can also import
siwpClientfrom@zig-zag/better-siwp/clientto avoid bundling server-side code (like@talismn/siwsandzod) in your client bundle. Both imports export the samesiwpClient.
Basic Setup
Add the siwpClient() plugin to your Better Auth client:
import { createAuthClient } from 'better-auth/client';
import { siwpClient } from '@zig-zag/better-siwp/client';
export const authClient = createAuthClient({
plugins: [siwpClient()],
});Client Methods
After adding siwpClient(), you get two methods on authClient.siwp:
// Request a nonce (configurable expiry, single use)
const { data, error } = await authClient.siwp.nonce({
walletAddress: '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
});
// data: { nonce: string }
// Verify a signed message and create a session
const { data, error } = await authClient.siwp.verify({
message: '...', // The prepared SIWS message string
signature: '0x...', // The wallet signature
walletAddress: '5Grw...', // The signing address
});
// data: { token: string, success: boolean, user: { id: string, walletAddress: string } }With LunoKit (Recommended)
LunoKit handles wallet connection and provides a useSignMessage hook that pairs perfectly with this plugin.
npm i @luno-kit/react @luno-kit/ui @tanstack/react-query @talismn/siwsSign-in function
import { SiwsMessage } from '@talismn/siws';
import { authClient } from '@/lib/auth-client';
export async function signInWithPolkadot(
address: string,
signMessage: (params: { message: string }) => Promise<{ signature: string }>
) {
// 1. Get a nonce from the server
const { data } = await authClient.siwp.nonce({ walletAddress: address });
// 2. Build a SIWS message
const siwsMessage = new SiwsMessage({
domain: window.location.host,
address,
statement: 'Sign in with your Polkadot wallet',
uri: window.location.origin,
version: '1.0.0',
nonce: data.nonce,
issuedAt: Date.now(),
expirationTime: Date.now() + 24 * 60 * 60 * 1000,
});
const message = siwsMessage.prepareMessage();
// 3. Sign via LunoKit's useSignMessage hook
const { signature } = await signMessage({ message });
// 4. Verify with the server — session cookie is set automatically
await authClient.siwp.verify({ message, signature, walletAddress: address });
}React component
import { useAccount, useSignMessage } from '@luno-kit/react';
import { useConnectModal } from '@luno-kit/ui';
import { signInWithPolkadot } from '@/lib/auth/polkadot-auth-client';
function AuthButton() {
const { account } = useAccount();
const { signMessageAsync } = useSignMessage();
const { open: openConnectModal } = useConnectModal();
if (!account) {
return <button onClick={openConnectModal}>Connect Wallet</button>;
}
return (
<button onClick={() => signInWithPolkadot(account.address, signMessageAsync)}>
Sign In
</button>
);
}With @polkadot/extension-dapp
If you prefer to work with the Polkadot.js extension APIs directly:
npm i @polkadot/extension-dapp @talismn/siwsimport { web3Enable, web3Accounts, web3FromAddress } from '@polkadot/extension-dapp';
import { SiwsMessage } from '@talismn/siws';
import { authClient } from '@/lib/auth-client';
async function signIn() {
// 1. Connect to wallet extensions
await web3Enable('My App');
const accounts = await web3Accounts();
const account = accounts[0];
// 2. Get nonce
const { data } = await authClient.siwp.nonce({ walletAddress: account.address });
// 3. Build and sign the message
const siwsMessage = new SiwsMessage({
domain: window.location.host,
address: account.address,
statement: 'Sign in with your Polkadot wallet',
uri: window.location.origin,
version: '1.0.0',
nonce: data.nonce,
issuedAt: Date.now(),
expirationTime: Date.now() + 24 * 60 * 60 * 1000,
});
const message = siwsMessage.prepareMessage();
const injector = await web3FromAddress(account.address);
const { signature } = await injector.signer.signRaw!({
address: account.address,
data: message,
type: 'bytes',
});
// 4. Verify — session is created server-side
await authClient.siwp.verify({ message, signature, walletAddress: account.address });
}See the examples page for complete working examples.