Powered by

A design system from Family

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 = {
// Required
getNonce: () => 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>;
// Optional
enabled?: boolean; // defaults true
nonceRefetchInterval?: number; // in milliseconds, defaults to 5 minutes
sessionRefetchInterval?: number; // in milliseconds, defaults to 5 minutes
signOutOnDisconnect?: boolean; // defaults true
signOutOnAccountChange?: boolean; // defaults true
signOutOnNetworkChange?: boolean; // defaults true
onSignIn?: (session?: SIWESession) => void;
onSignOut?: () => void;
};
getNonce
() => Promise<string>
Required
createMessage
(args: { nonce: string; address: string; chainId: number }) => string
Required
verifyMessage
(args: { message: string; signature: string }) => Promise<boolean>
Required
getSession
() => Promise<SIWESession | null>
Required
signOut
Promise<boolean>
Required
enabled
boolean
Default is true

Whether or not to enable SIWE.

nonceRefetchInterval
number
Default is 300000

How often to refetch the nonce, in milliseconds.

sessionRefetchInterval
number
Default is 300000

How often to refetch the session, in milliseconds.

signOutOnDisconnect
boolean
Default is true

Whether or not to sign out when the user disconnects their wallet.

signOutOnAccountChange
boolean
Default is true

Keeps SIWE session matching connected account

signOutOnNetworkChange
boolean
Default is true

Keeps the SIWE session and the connected account/network in sync

onSignIn
function

Callback when user signs in

onSignOut
function

Callback when user signs out