SIWE — Custom Implementation
ConnectKit provides a streamlined user experience when implementing Sign In With Ethereum (SIWE) to your app.
If using ConnectKit with Next.js, we recommend using our Next.js + Sign In With Ethereum package.
1. Install
Once you've set up ConnectKit, install the official Sign In With Ethereum package.
Terminal
npm install viem@">=2.12.0"
2. Configure
ConnectKit exports the SIWEProvider and the SIWEConfig type to smoothly integrate with your backend and session management setup.
import { SIWEProvider, SIWEConfig, ConnectKitProvider } from 'connectkit';import { SiweMessage } from 'siwe';const siweConfig: SIWEConfig = {...};<SIWEProvider {...siweConfig}><ConnectKitProvider>...</ConnectKitProvider></SIWEProvider>
The getNonce method acts like a CSRF token to avoid spoofing. The siwe package exports a generateNonce() helper, or you can use a CSRF token if your backend already has one.
ConnectKit will poll this endpoint (every 5 minutes by default, configured by nonceRefetchInterval), so it's recommended to create the nonce once and remove it or regenerate it once used (see the connectkit-next-siwe package on GitHub for an example).
const siweConfig: SIWEConfig = {...getNonce: async () => fetch('/api/siwe/nonce').then((res) => res.text()),...};
The official siwe package provides an easy way to create an EIP-4361-compatible message that can later be verified with the same package. The nonce argument comes from your getNonce endpoint, while the address and chainId variables come from the currently connected wallet.
const siweConfig: SIWEConfig = {...createMessage: ({ nonce, address, chainId }) => new SiweMessage({version: '1',domain: window.location.host,uri: window.location.origin,address,chainId,nonce,// Human-readable ASCII assertion that the user will sign, and it must not contain `\n`.statement: 'Sign in With Ethereum.',}).prepareMessage(),...};
The verifyMessage method should lean on the siwe package's new SiweMessage(message).validate(signature) to ensure the message is valid, has not been tampered with, and has been appropriately signed by the wallet address.
const siweConfig: SIWEConfig = {...verifyMessage: async ({ message, signature }) => fetch('/api/siwe/verify', {method: 'POST',headers: {'Content-Type': 'application/json',},body: JSON.stringify({ message, signature }),}).then((res) => res.ok),...};
The backend session should store the associated address and chainId and return it via the getSession method.
const siweConfig: SIWEConfig = {...getSession: async () => fetch('/api/siwe/session').then((res) => res.ok ? res.json() : null),...};
The users session can be destroyed calling signOut.
const siweConfig: SIWEConfig = {...signOut: async () => fetch('/api/siwe/logout').then((res) => res.ok),...};
Each of these methods should throw an error if the fetch fails or experiences backend fails (e.g connection issues, invalid request, invalid session, expired session, etc.). This will allow ConnectKit to gracefully handle errors when syncing the user experience with your backend.
And that's it—the ConnectKit modal will now automatically walk your users through how to Sign In With Ethereum after connecting their wallet to your app.
API Reference
type SIWEConfig = {// RequiredgetNonce: () => Promise<string>;createMessage: (args: { nonce: string; address: string; chainId: number }) => string;verifyMessage: (args: { message: string; signature: string }) => Promise<boolean>;getSession: () => Promise<SIWESession | null>;signOut: () => Promise<boolean>;// Optionalenabled?: boolean; // defaults truenonceRefetchInterval?: number; // in milliseconds, defaults to 5 minutessessionRefetchInterval?: number; // in milliseconds, defaults to 5 minutessignOutOnDisconnect?: boolean; // defaults truesignOutOnAccountChange?: boolean; // defaults truesignOutOnNetworkChange?: boolean; // defaults trueonSignIn?: (session?: SIWESession) => void;onSignOut?: () => void;};