import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode, } from "react"; import { useToast } from "../components/Toast"; import { useI18n } from "../i18n"; import { fetchWalletMe } from "./api"; import { signInWithInjectedWallet, type WalletKind } from "./injected"; import { clearWalletToken, readWalletToken, writeWalletToken } from "./token"; type WalletStatus = "loading" | "loggedOut" | "loggedIn"; const localWalletTokenPrefix = "local-wallet:"; export function localWalletToken(wallet: string): string { return `${localWalletTokenPrefix}${wallet}`; } function walletFromLocalToken(token: string): string | null { return token.startsWith(localWalletTokenPrefix) ? token.slice(localWalletTokenPrefix.length) : null; } type WalletContextValue = { address: string | null; token: string | null; status: WalletStatus; loginModalOpen: boolean; openLoginModal: () => void; closeLoginModal: () => void; signInInjected: (kind?: WalletKind) => Promise; completeLogin: (token: string, wallet: string) => void; logout: () => void; }; const WalletContext = createContext(null); export function shortenAddress(address: string): string { if (address.length <= 12) return address; return `${address.slice(0, 6)}...${address.slice(-4)}`; } export function WalletProvider({ children }: { children: ReactNode }) { const { t } = useI18n(); const { showToast } = useToast(); const [token, setToken] = useState(() => readWalletToken()); const [address, setAddress] = useState(null); const [status, setStatus] = useState( token ? "loading" : "loggedOut", ); const [loginModalOpen, setLoginModalOpen] = useState(false); useEffect(() => { let cancelled = false; if (!token) { setStatus("loggedOut"); setAddress(null); return; } const localWallet = walletFromLocalToken(token); if (localWallet) { setAddress(localWallet); setStatus("loggedIn"); return; } setStatus("loading"); fetchWalletMe(token) .then((me) => { if (cancelled) return; setAddress(me.wallet); setStatus("loggedIn"); }) .catch(() => { if (cancelled) return; clearWalletToken(); setToken(null); setAddress(null); setStatus("loggedOut"); }); return () => { cancelled = true; }; }, [token]); const completeLogin = useCallback((nextToken: string, wallet: string) => { writeWalletToken(nextToken); setToken(nextToken); setAddress(wallet); setStatus("loggedIn"); setLoginModalOpen(false); }, []); const signInInjected = useCallback( async (kind?: WalletKind) => { try { const res = await signInWithInjectedWallet(kind); completeLogin(res.token, res.wallet); showToast(t("walletLoginSuccess")); } catch (error) { const message = error instanceof Error ? error.message : t("walletLoginFailed"); showToast(message || t("walletLoginFailed"), "error"); throw error; } }, [completeLogin, showToast, t], ); const logout = useCallback(() => { clearWalletToken(); setToken(null); setAddress(null); setStatus("loggedOut"); showToast(t("walletDisconnected")); }, [showToast, t]); const value = useMemo( () => ({ address, token, status, loginModalOpen, openLoginModal: () => setLoginModalOpen(true), closeLoginModal: () => setLoginModalOpen(false), signInInjected, completeLogin, logout, }), [ address, completeLogin, loginModalOpen, logout, signInInjected, status, token, ], ); return ( {children} ); } export function useWallet() { const ctx = useContext(WalletContext); if (!ctx) throw new Error("useWallet must be used within WalletProvider"); return ctx; }