Last active
August 12, 2024 19:40
-
-
Save kamescg/50580ab7047f7c82b5103a92080b9dbd to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use client" | |
import { useEffect } from "react" | |
import { atom, useAtom } from "jotai" | |
import { useMutation } from "@tanstack/react-query" | |
import { useAccount, useSignMessage } from "wagmi" | |
import { Address } from "viem" | |
import { createSiweMessage } from "viem/siwe" | |
import { SignMessageData, SignMessageVariables } from "wagmi/query" | |
import { BASE_URL, siteConfig } from "@/config/site" | |
// We use an atom to store the nonce in case multiple components are mounted at the same time. | |
// If we don't use an atom, the session.cookie gets out of sync and is invalid during verification. | |
// This is because multiple /api/siwe/nonce requests would be made and the nonce would be different. | |
const nonceAtom = atom() | |
export function useSiweSignIn() { | |
const { signMessageAsync } = useSignMessage() | |
const { address } = useAccount() | |
const { chain } = useAccount() | |
const [nonce, setNonce] = useAtom(nonceAtom) | |
// This is a required work-around for Coinbase Smart Wallet on IOS Safari. | |
// We optimistically fetch the nonce from the server to avoid blocking the pop-up. | |
useEffect(() => { | |
if (!nonce) { | |
;(async () => { | |
const nonceRes = await fetch(`/api/siwe/nonce`) | |
setNonce(await nonceRes.text()) | |
})().catch(console.error) | |
} | |
}, [nonce]) | |
return useMutation({ | |
mutationKey: ["sign-in-user"], | |
mutationFn: async () => { | |
if (!address || !chain?.id) return | |
const { message, signature } = await siweMessage({ | |
address, | |
chainId: chain?.id, | |
signMessageAsync, | |
nonce: nonce as string, | |
}) | |
const response = await fetch("/api/siwe/verify", { | |
method: "POST", | |
headers: { | |
"Content-Type": "application/json", | |
}, | |
body: JSON.stringify({ message, signature }), | |
}) | |
if (!response.ok) { | |
const data = await response.json() | |
throw new Error(data.message) | |
} | |
const data = await response.json() | |
return data | |
}, | |
onSuccess: () => { | |
}, | |
onError: (error) => { | |
}, | |
}) | |
} | |
interface SiweMessageOptions { | |
address: Address | |
chainId: number | |
nonce?: string | |
/* eslint-disable no-unused-vars */ | |
signMessageAsync: (args: SignMessageVariables) => Promise<SignMessageData> | |
} | |
/** | |
* Utility function to create and sign a SIWE message | |
* @param address - Ethereum address | |
* @param chainId - Ethereum chain ID | |
* @param signMessageAsync - Wallet sign message function | |
* @returns SIWE message, message to sign and signature | |
*/ | |
export const siweMessage = async ({ | |
address, | |
chainId, | |
nonce, | |
signMessageAsync, | |
}: SiweMessageOptions) => { | |
// If nonce is not provided, fetch it from the API. This is last resort. | |
if (!nonce) { | |
// 1. Get random nonce from API | |
const nonceRes = await fetch("/api/siwe/nonce") | |
nonce = await nonceRes.text() | |
} | |
// 2. Create SIWE message with pre-fetched nonce and sign with wallet | |
const host = window.location.host | |
.replace("https://", "") | |
.replace("http://", "") | |
const DOMAIN = host.startsWith("localhost") | |
? BASE_URL.replace("http://", "") | |
: host | |
const message = createSiweMessage({ | |
// localhost is not a valid RFC 3986 authority, hence not a valid domain: https://www.rfc-editor.org/rfc/rfc3986 | |
domain: DOMAIN, | |
address, | |
statement: `Sign in with Ethereum to ${siteConfig.name}`, | |
uri: window.location.origin, | |
version: "1", | |
chainId: chainId, | |
nonce: nonce, | |
}) | |
// 4. Sign message | |
const signature = await signMessageAsync({ | |
message, | |
}) | |
return { | |
message, | |
signature, | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment