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
1npm install siwe
2

Configure

ConnectKit exports the SIWEProvider and the SIWEConfig type to smoothly integrate with your backend and session management setup.

1import { SIWEProvider, SIWEConfig, ConnectKitProvider } from 'connectkit';
2import { SiweMessage } from 'siwe';
3
4const siweConfig: SIWEConfig = {
5 ...
6};
7
8<SIWEProvider {...siweConfig}>
9 <ConnectKitProvider>
10 ...
11 </ConnectKitProvider>
12</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).

1const siweConfig: SIWEConfig = {
2 ...
3 getNonce: async () => fetch('/api/siwe/nonce').then((res) => res.text()),
4 ...
5};

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.

1const siweConfig: SIWEConfig = {
2 ...
3 createMessage: ({ nonce, address, chainId }) => new SiweMessage({
4 version: '1',
5 domain: window.location.host,
6 uri: window.location.origin,
7 address,
8 chainId,
9 nonce,
10 statement: 'Sign in With Ethereum.',
11 }).prepareMessage(),
12 ...
13};

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.

1const siweConfig: SIWEConfig = {
2 ...
3 verifyMessage: async ({ message, signature }) => fetch('/api/siwe/verify', {
4 method: 'POST',
5 headers: {
6 'Content-Type': 'application/json',
7 },
8 body: JSON.stringify({ message, signature }),
9 }).then((res) => res.ok),
10 ...
11};

The backend session should store the associated address and chainId and return it via the getSession method.

1const siweConfig: SIWEConfig = {
2 ...
3 getSession: async () => fetch('/api/siwe/session').then((res) => res.ok ? res.json() : null),
4 ...
5};

The users session can be destroyed calling signOut.

1const siweConfig: SIWEConfig = {
2 ...
3 signOut: async () => fetch('/api/siwe/logout').then((res) => res.ok),
4 ...
5};

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

1type SIWEConfig = {
2 // Required
3 getNonce: () => Promise<string>;
4 createMessage: (args: { nonce: string; address: string; chainId: number }) => string;
5 verifyMessage: (args: { message: string; signature: string }) => Promise<boolean>;
6 getSession: () => Promise<SIWESession | null>;
7 signOut: () => Promise<boolean>;
8 // Optional
9 enabled?: boolean; // defaults true
10 nonceRefetchInterval?: number; // in seconds, defaults to 5 minutes
11 sessionRefetchInterval?: number; // in seconds, defaults to 5 minutes
12 signOutOnDisconnect?: boolean; // defaults true
13 signOutOnAccountChange?: boolean; // defaults true
14 signOutOnNetworkChange?: boolean; // defaults true
15};
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
nonceRefetchInterval
number (in seconds)
Default is 300000
sessionRefetchInterval
number (in seconds)
Default is 300000
signOutOnDisconnect
boolean
Default is true
signOutOnAccountChange
boolean
Default is true
signOutOnNetworkChange
boolean
Default is true