Skip to content

Commit 33eefb1

Browse files
authored
update: use viem for siwe (#387)
* viem siwe * wagmi fix * cleanup * handle all chains * use parseSiweMessage * types * types * Update package.json * user defined chain * add public client * include type * use config * update packages * bump versions + fix types * update packages * better error information * fix merge * add async support ( #324 ) * versions * Update CHANGELOG.md * bump viem * bump * dep fix * fix testebench * deo
1 parent 97ba11b commit 33eefb1

File tree

12 files changed

+126
-561
lines changed

12 files changed

+126
-561
lines changed

CHANGELOG.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
21
# x.y.z
32

4-
This update adds support for [Coinbase Smart Wallet](https://smartwallet.dev) and adds additional support for the latest versions of peer dependencies `wagmi` and `viem`.
3+
This update adds support for [Coinbase Smart Wallet](https://smartwallet.dev), adds additional support for the latest versions of peer dependencies `wagmi` and `viem`, and removes the dependency `ethers` from `connectkit-next-siwe` in favor of `viem`'s [SIWE implementation](https://viem.sh/docs/siwe/actions/verifySiweMessage).
54

65
## New
76

@@ -10,6 +9,11 @@ This update adds support for [Coinbase Smart Wallet](https://smartwallet.dev) an
109
## Updated
1110

1211
- Changed default setting for `enforceSupportedChains` to `false` to allow for a better default user and developer experience.
12+
- Updates peer dependency `viem` to `>=2.13.x`.
13+
14+
## Deprecated
15+
16+
- Removes dependency `ethers` from `connectkit-next-siwe` in favor of `viem`'s [SIWE implementation](https://viem.sh/docs/siwe/actions/verifySiweMessage).
1317

1418
# 1.7.3
1519

examples/nextjs-siwe/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
"dependencies": {
1212
"connectkit": "workspace:packages/connectkit",
1313
"connectkit-next-siwe": "workspace:packages/connectkit-next-siwe",
14-
"ethers": "^5",
1514
"next": "12.3.0",
1615
"react": "^18.0.0",
1716
"react-dom": "^18.0.0",

examples/testbench/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,11 @@
1212
"@tanstack/react-query": "^5.17.10",
1313
"connectkit": "workspace:packages/connectkit",
1414
"connectkit-next-siwe": "workspace:packages/connectkit-next-siwe",
15-
"ethers": "^5",
1615
"local-ssl-proxy": "^1.3.0",
1716
"next": "14.1.0",
1817
"react": "^18.0.0",
1918
"react-dom": "^18.0.0",
20-
"viem": "^2.13.0",
19+
"viem": "^2.13.6",
2120
"wagmi": "^2.9.7"
2221
},
2322
"devDependencies": {

examples/testbench/src/components/Web3Provider.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const avalanche: Chain = defineChain({
2727
testnet: false,
2828
});
2929

30-
const ckConfig = getDefaultConfig({
30+
export const ckConfig = getDefaultConfig({
3131
/*
3232
chains: [
3333
mainnet,
@@ -48,6 +48,7 @@ const customConfig = {
4848
connectors: [wallets['rainbow'], ...(ckConfig.connectors ?? [])],
4949
};
5050
const config = createConfig(ckConfig);
51+
5152
const queryClient = new QueryClient();
5253

5354
type ContextValue = {};

examples/testbench/src/pages/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ const Home: NextPage = () => {
319319
</div>
320320
<h2>dApps configured chains</h2>
321321
<div style={{ display: 'flex', gap: 8 }}>
322-
{chains.map((chain: wagmiChains.Chain) => (
322+
{chains.map((chain) => (
323323
<ChainIcon key={chain.id} id={chain.id} />
324324
))}
325325
</div>

examples/testbench/src/utils/siweServer.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { configureServerSideSIWE } from 'connectkit-next-siwe';
2+
import { ckConfig } from '../components/Web3Provider';
23

34
export const siweServer = configureServerSideSIWE({
5+
config: {
6+
chains: ckConfig.chains,
7+
transports: ckConfig.transports,
8+
},
49
options: {
510
afterLogout: async () => console.log('afterLogout'),
611
afterNonce: async () => console.log('afterNonce'),

packages/connectkit-next-siwe/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "connectkit-next-siwe",
3-
"version": "0.2.0",
3+
"version": "0.3.0",
44
"author": "Family",
55
"homepage": "https://docs.family.co/connectkit",
66
"license": "BSD-2-Clause license",
@@ -37,14 +37,14 @@
3737
],
3838
"dependencies": {
3939
"iron-session": "^6.2.1",
40-
"siwe": "^2.1.4"
40+
"viem": "^2.13.0"
4141
},
4242
"peerDependencies": {
4343
"connectkit": ">=1.2.0",
4444
"next": ">=12.x",
4545
"react": "17.x || 18.x",
4646
"react-dom": "17.x || 18.x",
47-
"siwe": ">=2"
47+
"viem": ">=2.13.3"
4848
},
4949
"devDependencies": {
5050
"@types/node": "^16.11.27",

packages/connectkit-next-siwe/src/configureSIWE.tsx

Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,14 @@ import { SIWEProvider } from 'connectkit';
33
import type { IncomingMessage, ServerResponse } from 'http';
44
import { getIronSession, IronSession, IronSessionOptions } from 'iron-session';
55
import { NextApiHandler, NextApiRequest, NextApiResponse } from 'next';
6-
import { generateNonce, SiweErrorType, SiweMessage } from 'siwe';
6+
7+
import { Chain, Transport, PublicClient, createPublicClient, http } from 'viem';
8+
import * as allChains from 'viem/chains';
9+
import {
10+
generateSiweNonce,
11+
createSiweMessage,
12+
parseSiweMessage,
13+
} from 'viem/siwe';
714

815
type RouteHandlerOptions = {
916
afterNonce?: (
@@ -23,10 +30,16 @@ type RouteHandlerOptions = {
2330
) => Promise<void>;
2431
afterLogout?: (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
2532
};
33+
2634
type NextServerSIWEConfig = {
35+
config?: {
36+
chains: readonly [Chain, ...Chain[]];
37+
transports?: Record<number, Transport>;
38+
};
2739
session?: Partial<IronSessionOptions>;
2840
options?: RouteHandlerOptions;
2941
};
42+
3043
type NextClientSIWEConfig = {
3144
apiRoutePrefix: string;
3245
statement?: string;
@@ -108,7 +121,7 @@ const nonceRoute = async (
108121
case 'GET':
109122
const session = await getSession(req, res, sessionConfig);
110123
if (!session.nonce) {
111-
session.nonce = generateNonce();
124+
session.nonce = generateSiweNonce();
112125
await session.save();
113126
}
114127
if (afterCallback) {
@@ -147,37 +160,57 @@ const verifyRoute = async (
147160
req: NextApiRequest,
148161
res: NextApiResponse<void>,
149162
sessionConfig: IronSessionOptions,
163+
config?: NextServerSIWEConfig['config'],
150164
afterCallback?: RouteHandlerOptions['afterVerify']
151165
) => {
152166
switch (req.method) {
153167
case 'POST':
154168
try {
155169
const session = await getSession(req, res, sessionConfig);
156-
const { message, signature } = req.body;
157-
const siweMessage = new SiweMessage(message);
158-
const { data: fields } = await siweMessage.verify({ signature, nonce: session.nonce });
159-
if (fields.nonce !== session.nonce) {
170+
const { message, signature } = req.body as {
171+
message: string;
172+
signature: `0x${string}`;
173+
};
174+
175+
const parsed = parseSiweMessage(message);
176+
if (parsed.nonce !== session.nonce) {
160177
return res.status(422).end('Invalid nonce.');
161178
}
162-
session.address = fields.address;
163-
session.chainId = fields.chainId;
179+
180+
let chain = config?.chains
181+
? Object.values(config.chains).find((c) => c.id === parsed.chainId)
182+
: undefined;
183+
if (!chain) {
184+
// Try to find chain from allChains if not found in user-provided chains
185+
chain = Object.values(allChains).find((c) => c.id === parsed.chainId);
186+
}
187+
if (!chain) {
188+
throw new Error('Chain not found.');
189+
}
190+
191+
const publicClient: PublicClient = createPublicClient({
192+
chain,
193+
transport: http(),
194+
});
195+
196+
const verified = await publicClient.verifySiweMessage({
197+
message,
198+
signature,
199+
nonce: session.nonce,
200+
});
201+
if (!verified) {
202+
return res.status(422).end('Unable to verify signature.');
203+
}
204+
205+
session.address = parsed.address;
206+
session.chainId = parsed.chainId;
164207
await session.save();
165208
if (afterCallback) {
166209
await afterCallback(req, res, session);
167210
}
168211
res.status(200).end();
169212
} catch (error) {
170-
switch (error) {
171-
case SiweErrorType.INVALID_NONCE:
172-
case SiweErrorType.INVALID_SIGNATURE: {
173-
res.status(422).end(String(error));
174-
break;
175-
}
176-
default: {
177-
res.status(400).end(String(error));
178-
break;
179-
}
180-
}
213+
res.status(400).end(String(error));
181214
}
182215
break;
183216
default:
@@ -195,6 +228,7 @@ const envVar = (name: string) => {
195228
};
196229

197230
export const configureServerSideSIWE = <TSessionData extends Object = {}>({
231+
config,
198232
session: { cookieName, password, cookieOptions, ...otherSessionOptions } = {},
199233
options: { afterNonce, afterVerify, afterSession, afterLogout } = {},
200234
}: NextServerSIWEConfig): ConfigureServerSIWEResult<TSessionData> => {
@@ -220,7 +254,7 @@ export const configureServerSideSIWE = <TSessionData extends Object = {}>({
220254
case 'nonce':
221255
return await nonceRoute(req, res, sessionConfig, afterNonce);
222256
case 'verify':
223-
return await verifyRoute(req, res, sessionConfig, afterVerify);
257+
return await verifyRoute(req, res, sessionConfig, config, afterVerify);
224258
case 'session':
225259
return await sessionRoute(req, res, sessionConfig, afterSession);
226260
case 'logout':
@@ -253,15 +287,15 @@ export const configureClientSIWE = <TSessionData extends Object = {}>({
253287
return nonce;
254288
}}
255289
createMessage={({ nonce, address, chainId }) =>
256-
new SiweMessage({
290+
createSiweMessage({
257291
version: '1',
258292
domain: window.location.host,
259293
uri: window.location.origin,
260294
address,
261295
chainId,
262296
nonce,
263297
statement,
264-
}).prepareMessage()
298+
})
265299
}
266300
verifyMessage={({ message, signature }) =>
267301
fetch(`${apiRoutePrefix}/verify`, {

packages/connectkit/src/components/Common/ChainSelectList/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useState } from 'react';
12
import { useAccount, useSwitchChain } from 'wagmi';
23
import { chainConfigs } from '../../../constants/chainConfigs';
34

@@ -20,7 +21,6 @@ import { isCoinbaseWalletConnector, isMobile } from '../../../utils';
2021
import ChainIcons from '../../../assets/chains';
2122
import useLocales from '../../../hooks/useLocales';
2223
import { useContext } from '../../ConnectKit';
23-
import { useState } from 'react';
2424

2525
const Spinner = (
2626
<svg
@@ -59,7 +59,7 @@ const ChainSelectList = ({
5959
variant?: 'primary' | 'secondary';
6060
}) => {
6161
const { connector, chain } = useAccount();
62-
const { chains, isPending, data, switchChain, error } = useSwitchChain();
62+
const { chains, isPending, switchChain, error } = useSwitchChain();
6363
const [pendingChainId, setPendingChainId] = useState<number | undefined>(
6464
undefined
6565
);

packages/connectkit/src/siwe/SIWEContext.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createContext } from 'react';
22
import { useQuery } from '@tanstack/react-query';
3+
import { Address } from 'viem';
34

45
export enum StatusState {
56
READY = 'ready',
@@ -10,7 +11,7 @@ export enum StatusState {
1011
}
1112

1213
export type SIWESession = {
13-
address: string;
14+
address: Address;
1415
chainId: number;
1516
};
1617

@@ -19,9 +20,9 @@ export type SIWEConfig = {
1920
getNonce: () => Promise<string>;
2021
createMessage: (args: {
2122
nonce: string;
22-
address: string;
23+
address: Address;
2324
chainId: number;
24-
}) => string;
25+
}) => Promise<string> | string;
2526
verifyMessage: (args: {
2627
message: string;
2728
signature: string;

0 commit comments

Comments
 (0)