import { useCallback, useEffect, useRef, useState } from "react"; import { useConnectModal } from "@rainbow-me/rainbowkit"; import { useAccount, useDisconnect, useSignMessage } from "wagmi"; import { requestWalletNonce, verifyWalletSignature } from "./api"; import { hasWalletConnectProjectId } from "./RainbowWalletProvider"; import { useWallet } from "./WalletProvider"; export type WalletConnectLoginState = "idle" | "connecting" | "signing"; /** * MetaMask / imToken QR fallback via RainbowKit + WalletConnect. * * Flow: open the RainbowKit connect modal (WalletConnect QR) -> once an account * is connected, request a nonce, sign it with `personal_sign` through wagmi, * verify against the backend and complete our own JWT login. The wagmi/WC * session is only needed for the signature, so we disconnect right after. * * Entirely gated behind a real `VITE_WALLETCONNECT_PROJECT_ID`: when it is * missing `available` is false and `start` is a no-op, so callers can hide or * disable the entry instead of triggering a connect with a fake project id. */ export function useWalletConnectLogin() { const available = hasWalletConnectProjectId(); const { completeLogin } = useWallet(); const { address, isConnected } = useAccount(); const { signMessageAsync } = useSignMessage(); const { disconnect } = useDisconnect(); const { openConnectModal } = useConnectModal(); const [state, setState] = useState("idle"); const [error, setError] = useState(""); const pendingRef = useRef(false); const reset = useCallback(() => { pendingRef.current = false; setState("idle"); setError(""); }, []); const start = useCallback(() => { if (!available) return; setError(""); pendingRef.current = true; setState("connecting"); // When already connected, openConnectModal is undefined; the effect below // picks up the existing account and proceeds straight to signing. openConnectModal?.(); }, [available, openConnectModal]); useEffect(() => { if (!pendingRef.current || !isConnected || !address) return; pendingRef.current = false; setState("signing"); let cancelled = false; void (async () => { try { const nonce = await requestWalletNonce(address); const signature = await signMessageAsync({ message: nonce.message }); const verified = await verifyWalletSignature({ address, message: nonce.message, signature, }); if (cancelled) return; completeLogin(verified.token, verified.wallet); setState("idle"); } catch (err) { if (!cancelled) { setError( err instanceof Error ? err.message : "WalletConnect login failed", ); setState("idle"); } } finally { // We only needed a one-off signature, not a persistent wagmi session. disconnect(); } })(); return () => { cancelled = true; }; }, [address, completeLogin, disconnect, isConnected, signMessageAsync]); return { available, state, error, start, reset }; }