diff --git a/demo/nextjs-ssr-app/app/layout.tsx b/demo/nextjs-ssr-app/app/layout.tsx
index c4e238f59..189321d3c 100644
--- a/demo/nextjs-ssr-app/app/layout.tsx
+++ b/demo/nextjs-ssr-app/app/layout.tsx
@@ -5,7 +5,7 @@ import { Inter } from "next/font/google";
import Provider from "@/components/provider";
import { cookieToWeb3AuthState } from "@web3auth/modal";
import { headers } from "next/headers";
-import { cookieToInitialState } from "wagmi";
+import { cookieToWagmiState } from "@web3auth/modal/react/wagmi";
const inter = Inter({ subsets: ["latin"] });
@@ -16,11 +16,13 @@ export const metadata = {
export default function RootLayout({ children }: { children: React.ReactNode }) {
const web3authInitialState = cookieToWeb3AuthState((headers()).get('cookie'))
- console.log("web3authInitialState", web3authInitialState)
+ const wagmiInitialState = cookieToWagmiState((headers()).get('cookie'))
+ console.log("web3authInitialState", web3authInitialState);
+ console.log("wagmiInitialState", wagmiInitialState);
return (
- {children}
+ {children}
);
diff --git a/demo/nextjs-ssr-app/components/Main.tsx b/demo/nextjs-ssr-app/components/Main.tsx
index 67ccf4c43..68f45e9ca 100644
--- a/demo/nextjs-ssr-app/components/Main.tsx
+++ b/demo/nextjs-ssr-app/components/Main.tsx
@@ -12,7 +12,7 @@ import {
useWeb3AuthDisconnect,
useWeb3AuthUser,
} from "@web3auth/modal/react";
-import { useAccount, useBalance, useSignMessage, useSignTypedData } from "wagmi";
+import { useAccount, useBalance, useChainId, useSignMessage, useSignTypedData, useSwitchChain } from "wagmi";
const Main = () => {
const { provider, isConnected } = useWeb3Auth();
@@ -29,16 +29,19 @@ const Main = () => {
const { showWalletConnectScanner, loading: isWalletConnectScannerLoading, error: walletConnectScannerError } = useWalletConnectScanner();
const { showWalletUI, loading: isWalletUILoading, error: walletUIError } = useWalletUI();
const { token, loading: isUserTokenLoading, error: userTokenError, authenticateUser } = useIdentityToken();
-
+ const { switchChainAsync, chains } = useSwitchChain();
+ const chainId = useChainId();
+
console.log("isConnected", isConnected);
-
+ console.log("isWagmiConnected", isWagmiConnected);
const loggedInView = (
<>
- {/*
Account Address: {address}
*/}
- {/*
Account Balance: {balance?.value}
*/}
+
Account Address: {address}
+
Account Balance: {balance?.formatted}
MFA Enabled: {isMFAEnabled ? "Yes" : "No"}
+
ConnectedChain ID: {chainId}
{/* User Info */}
@@ -177,6 +180,22 @@ const Main = () => {
{signedTypedDataData &&
}
+ {/* Chain Actions */}
+
+
Switch Chain
+ {chains.map((chain) => (
+
+ ))}
+
+
{/* Disconnect */}
Logout
@@ -205,7 +224,7 @@ const Main = () => {
Web3Auth: {isConnected ? "Connected" : "Disconnected"}
Wagmi: {isWagmiConnected ? "Connected" : "Disconnected"}
- {provider ? loggedInView : unloggedInView}
+ {isConnected ? loggedInView : unloggedInView}
);
};
diff --git a/demo/nextjs-ssr-app/components/provider.tsx b/demo/nextjs-ssr-app/components/provider.tsx
index bf39aad99..cc6a9c37e 100644
--- a/demo/nextjs-ssr-app/components/provider.tsx
+++ b/demo/nextjs-ssr-app/components/provider.tsx
@@ -5,6 +5,7 @@ import { IWeb3AuthState } from "@web3auth/modal";
import { type Web3AuthContextConfig } from "@web3auth/modal/react";
import { Web3AuthProvider } from "@web3auth/modal/react";
import { WagmiProvider } from "@web3auth/modal/react/wagmi";
+import { State } from "wagmi";
const queryClient = new QueryClient();
const clientId = "BKZDJP0ouZP0PtfQYssMiezINbUwnIthw6ClTtTICvh0MCRgAxi5GJbHKH9cjM6xyWxe73c6c94ASCTxbGNLUt8";
@@ -18,11 +19,11 @@ const web3authConfig: Web3AuthContextConfig = {
ssr: true
},
};
-export default function Provider({ children, web3authInitialState }: { children: React.ReactNode, web3authInitialState: IWeb3AuthState | undefined }) {
+export default function Provider({ children, web3authInitialState, wagmiInitialState }: { children: React.ReactNode, web3authInitialState: IWeb3AuthState | undefined, wagmiInitialState: State | undefined }) {
return (
-
+
{children}
diff --git a/demo/nextjs-ssr-app/next.config.js b/demo/nextjs-ssr-app/next.config.js
index 82b6dba6e..1d7b31b2f 100644
--- a/demo/nextjs-ssr-app/next.config.js
+++ b/demo/nextjs-ssr-app/next.config.js
@@ -7,6 +7,7 @@ const nextConfig = {
config.resolve.alias = {
...(config.resolve.alias || {}),
wagmi: path.resolve(__dirname, 'node_modules/wagmi'),
+ 'pino-pretty': path.resolve(__dirname, 'node_modules/pino-pretty'), // add this to remove console errors.
};
return config;
},
diff --git a/packages/modal/src/react/hooks/useWeb3AuthUser.ts b/packages/modal/src/react/hooks/useWeb3AuthUser.ts
index 448d250bb..66f8edc46 100644
--- a/packages/modal/src/react/hooks/useWeb3AuthUser.ts
+++ b/packages/modal/src/react/hooks/useWeb3AuthUser.ts
@@ -12,7 +12,7 @@ export interface IUseWeb3AuthUser {
}
export const useWeb3AuthUser = (): IUseWeb3AuthUser => {
- const { web3Auth, isConnected, isMFAEnabled, setIsMFAEnabled } = useWeb3AuthInner();
+ const { web3Auth, isConnected, isInitialized, isMFAEnabled, setIsMFAEnabled } = useWeb3AuthInner();
const [userInfo, setUserInfo] = useState | null>(null);
const [loading, setLoading] = useState(false);
@@ -39,13 +39,15 @@ export const useWeb3AuthUser = (): IUseWeb3AuthUser => {
setIsMFAEnabled(userInfo?.isMfaEnabled || false);
};
- if (isConnected && !userInfo) saveUserInfo();
+ // In SSR mode, isConnected is true on the first render, but userInfo is not populated.
+ // hence we also need to check for isInitialized to ensure that userInfo is populated.
+ if (isInitialized && isConnected && !userInfo) saveUserInfo();
if (!isConnected && userInfo) {
setUserInfo(null);
setIsMFAEnabled(false);
}
- }, [isConnected, userInfo, getUserInfo, setIsMFAEnabled]);
+ }, [isInitialized, isConnected, userInfo, getUserInfo, setIsMFAEnabled]);
return { loading, error, userInfo, isMFAEnabled, getUserInfo };
};
diff --git a/packages/modal/src/react/wagmi/connector.ts b/packages/modal/src/react/wagmi/connector.ts
new file mode 100644
index 000000000..1484819f8
--- /dev/null
+++ b/packages/modal/src/react/wagmi/connector.ts
@@ -0,0 +1,127 @@
+import { ChainNotConfiguredError, createConnector, type CreateConnectorFn } from "@wagmi/core";
+import { CONNECTOR_EVENTS, type IProvider } from "@web3auth/no-modal";
+import { Address, type Chain, getAddress, SwitchChainError } from "viem";
+
+import { type Web3Auth } from "../../modalManager";
+
+export const WEB3AUTH_CONNECTOR_ID = "web3auth";
+
+export function createWeb3AuthConnector(web3auth: Web3Auth): CreateConnectorFn {
+ return createConnector((config) => ({
+ id: WEB3AUTH_CONNECTOR_ID,
+ name: "Web3Auth",
+ type: "web3auth",
+ async connect({ isReconnecting }) {
+ config.emitter.emit("message", {
+ type: "connecting",
+ });
+
+ if (isReconnecting) {
+ const accounts = await this.getAccounts().catch(() => []);
+ return {
+ accounts: accounts,
+ chainId: Number(web3auth.currentChainId),
+ };
+ }
+
+ const provider = await web3auth.connect();
+ if (provider) {
+ const accounts = await this.getAccounts();
+ return {
+ accounts: accounts,
+ chainId: Number(provider.chainId),
+ };
+ }
+ return {
+ accounts: [],
+ chainId: 0,
+ };
+ },
+ async getAccounts(): Promise {
+ const provider = await this.getProvider();
+ return (
+ await provider.request({
+ method: "eth_accounts",
+ })
+ ).map((x: string) => getAddress(x));
+ },
+ async getChainId() {
+ const provider = await this.getProvider();
+ return Number(provider.chainId);
+ },
+ async getProvider(): Promise {
+ if (web3auth.status !== "not_ready") {
+ if (web3auth.provider) {
+ const provider = web3auth.provider;
+ provider.on("accountsChanged", this.onAccountsChanged);
+ provider.on("chainChanged", this.onChainChanged);
+ provider.on("disconnect", this.onDisconnect.bind(this));
+ return web3auth.provider;
+ }
+
+ return web3auth.provider;
+ }
+
+ // else wait for web3auth to be ready.
+ return new Promise((resolve) => {
+ const handleReadyEvent = () => {
+ web3auth.off(CONNECTOR_EVENTS.READY, handleReadyEvent);
+ web3auth.off(CONNECTOR_EVENTS.CONNECTED, handleConnectedEvent);
+ resolve(web3auth.provider);
+ };
+
+ const handleConnectedEvent = () => {
+ web3auth.off(CONNECTOR_EVENTS.CONNECTED, handleConnectedEvent);
+ web3auth.off(CONNECTOR_EVENTS.READY, handleReadyEvent);
+ resolve(web3auth.provider);
+ };
+
+ web3auth.on(CONNECTOR_EVENTS.READY, handleReadyEvent);
+ web3auth.on(CONNECTOR_EVENTS.CONNECTED, handleConnectedEvent);
+ });
+ },
+ async isAuthorized() {
+ try {
+ const accounts = await this.getAccounts();
+ return !!accounts.length;
+ } catch {
+ return false;
+ }
+ },
+ async switchChain({ chainId }): Promise {
+ try {
+ const chain = config.chains.find((x) => x.id === chainId);
+ if (!chain) throw new SwitchChainError(new ChainNotConfiguredError());
+
+ await web3auth.switchChain({ chainId: `0x${chain.id.toString(16)}` });
+ config.emitter.emit("change", {
+ chainId,
+ });
+ return chain;
+ } catch (error: unknown) {
+ throw new SwitchChainError(error as Error);
+ }
+ },
+ async disconnect() {
+ await web3auth.logout();
+ const provider = await this.getProvider();
+ provider.removeListener("accountsChanged", this.onAccountsChanged);
+ provider.removeListener("chainChanged", this.onChainChanged);
+ provider.removeListener("disconnect", this.onDisconnect);
+ },
+ onAccountsChanged(accounts) {
+ if (accounts.length === 0) config.emitter.emit("disconnect");
+ else
+ config.emitter.emit("change", {
+ accounts: accounts.map((x) => getAddress(x)),
+ });
+ },
+ onChainChanged(chain) {
+ const chainId = Number(chain);
+ config.emitter.emit("change", { chainId });
+ },
+ onDisconnect(): void {
+ config.emitter.emit("disconnect");
+ },
+ }));
+}
diff --git a/packages/modal/src/react/wagmi/constants.ts b/packages/modal/src/react/wagmi/constants.ts
index be9ac818d..5404d9fca 100644
--- a/packages/modal/src/react/wagmi/constants.ts
+++ b/packages/modal/src/react/wagmi/constants.ts
@@ -1,11 +1,11 @@
-import { createConfig, http } from "wagmi";
+import { CreateConfigParameters, http } from "wagmi";
import { mainnet } from "wagmi/chains";
-export const defaultWagmiConfig = createConfig({
+export const defaultWagmiConfig: CreateConfigParameters = {
chains: [mainnet],
connectors: [], // or your basic wallets
ssr: true,
transports: {
[mainnet.id]: http(mainnet.rpcUrls.default.http[0]),
},
-});
+};
diff --git a/packages/modal/src/react/wagmi/index.ts b/packages/modal/src/react/wagmi/index.ts
index e25520636..4e2f2dd7d 100644
--- a/packages/modal/src/react/wagmi/index.ts
+++ b/packages/modal/src/react/wagmi/index.ts
@@ -1,2 +1,3 @@
export * from "./interface";
export * from "./provider";
+export { cookieToWagmiState } from "./utils";
diff --git a/packages/modal/src/react/wagmi/provider.ts b/packages/modal/src/react/wagmi/provider.ts
index 79dd1702a..566f47336 100644
--- a/packages/modal/src/react/wagmi/provider.ts
+++ b/packages/modal/src/react/wagmi/provider.ts
@@ -1,3 +1,5 @@
+"use client";
+
import { CHAIN_NAMESPACES, log, WalletInitializationError } from "@web3auth/no-modal";
import { createElement, Fragment, PropsWithChildren, useEffect, useMemo } from "react";
import { type Chain, defineChain, http, webSocket } from "viem";
@@ -5,41 +7,27 @@ import {
Config,
Connection,
Connector,
+ cookieStorage,
createConfig as createWagmiConfig,
type CreateConfigParameters,
CreateConnectorFn,
+ createStorage,
useAccountEffect,
useConfig as useWagmiConfig,
useReconnect,
WagmiProvider as WagmiProviderBase,
} from "wagmi";
-import { injected } from "wagmi/connectors";
import { useWeb3Auth, useWeb3AuthDisconnect } from "../hooks";
+import { createWeb3AuthConnector, WEB3AUTH_CONNECTOR_ID } from "./connector";
import { defaultWagmiConfig } from "./constants";
import { WagmiProviderProps } from "./interface";
-const WEB3AUTH_CONNECTOR_ID = "web3auth";
-
// Helper to initialize connectors for the given wallets
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-async function setupConnector(provider: any, config: Config) {
- let connector: Connector | CreateConnectorFn = config.connectors.find((c) => c.id === WEB3AUTH_CONNECTOR_ID);
+async function setupConnector(config: Config) {
+ const connector: Connector | CreateConnectorFn = config.connectors.find((c) => c.id === WEB3AUTH_CONNECTOR_ID);
if (connector) return connector;
-
- // Create new connector if not already existing
- connector = injected({
- target: {
- provider: provider,
- id: WEB3AUTH_CONNECTOR_ID,
- name: "Web3Auth",
- },
- });
-
- const result = config._internal.connectors.setup(connector);
- config._internal.connectors.setState((current) => [...current, result]);
- return result;
}
// Helper to connect a wallet and update wagmi state
@@ -100,7 +88,7 @@ function Web3AuthWagmiProvider({ children }: PropsWithChildren) {
useEffect(() => {
(async () => {
if (isConnected && provider) {
- const connector = await setupConnector(provider, wagmiConfig);
+ const connector = await setupConnector(wagmiConfig);
if (!connector) {
log.error("Failed to setup react wagmi connector");
throw new Error("Failed to setup connector");
@@ -123,59 +111,70 @@ export function WagmiProvider({ children, ...props }: PropsWithChildren {
- if (!isInitialized) return defaultWagmiConfig;
-
- const finalConfig: CreateConfigParameters = {
- ssr: true,
- ...config,
- chains: undefined,
- connectors: [],
- transports: {},
- multiInjectedProviderDiscovery: false,
- client: undefined,
- };
-
- const wagmiChains: Chain[] = [];
- if (isInitialized && web3Auth?.coreOptions?.chains) {
- const defaultChainId = web3Auth.currentChain?.chainId;
- const chains = web3Auth.coreOptions.chains.filter((chain) => chain.chainNamespace === CHAIN_NAMESPACES.EIP155);
- if (chains.length === 0) throw WalletInitializationError.invalidParams("No valid chains found in web3auth config for wagmi.");
-
- chains.forEach((chain) => {
- const wagmiChain = defineChain({
- id: Number.parseInt(chain.chainId, 16), // id in number form
- name: chain.displayName,
- rpcUrls: {
- default: {
- http: [chain.rpcTarget],
- webSocket: [chain.wsTarget],
+ const wagmiConfig = useMemo(() => {
+ let finalConfig: CreateConfigParameters;
+ const web3authConnector = createWeb3AuthConnector(web3Auth);
+ if (!isInitialized) {
+ defaultWagmiConfig.connectors = [web3authConnector];
+ finalConfig = defaultWagmiConfig;
+ } else {
+ finalConfig = {
+ ssr: true,
+ ...config,
+ chains: undefined,
+ connectors: [web3authConnector],
+ transports: {},
+ multiInjectedProviderDiscovery: false,
+ client: undefined,
+ };
+
+ const wagmiChains: Chain[] = [];
+ if (isInitialized && web3Auth?.coreOptions?.chains) {
+ const defaultChainId = web3Auth.currentChain?.chainId;
+ const chains = web3Auth.coreOptions.chains.filter((chain) => chain.chainNamespace === CHAIN_NAMESPACES.EIP155);
+ if (chains.length === 0) throw WalletInitializationError.invalidParams("No valid chains found in web3auth config for wagmi.");
+
+ chains.forEach((chain) => {
+ const wagmiChain = defineChain({
+ id: Number.parseInt(chain.chainId, 16), // id in number form
+ name: chain.displayName,
+ rpcUrls: {
+ default: {
+ http: [chain.rpcTarget],
+ webSocket: [chain.wsTarget],
+ },
+ },
+ blockExplorers: chain.blockExplorerUrl
+ ? {
+ default: {
+ name: "explorer", // TODO: correct name if chain config has it
+ url: chain.blockExplorerUrl,
+ },
+ }
+ : undefined,
+ nativeCurrency: {
+ name: chain.tickerName,
+ symbol: chain.ticker,
+ decimals: chain.decimals || 18,
},
- },
- blockExplorers: chain.blockExplorerUrl
- ? {
- default: {
- name: "explorer", // TODO: correct name if chain config has it
- url: chain.blockExplorerUrl,
- },
- }
- : undefined,
- nativeCurrency: {
- name: chain.tickerName,
- symbol: chain.ticker,
- decimals: chain.decimals || 18,
- },
+ });
+
+ if (defaultChainId === chain.chainId) {
+ wagmiChains.unshift(wagmiChain);
+ } else {
+ wagmiChains.push(wagmiChain);
+ }
+ finalConfig.transports[wagmiChain.id] = chain.wsTarget ? webSocket(chain.wsTarget) : http(chain.rpcTarget);
});
- if (defaultChainId === chain.chainId) {
- wagmiChains.unshift(wagmiChain);
- } else {
- wagmiChains.push(wagmiChain);
- }
- finalConfig.transports[wagmiChain.id] = chain.wsTarget ? webSocket(chain.wsTarget) : http(chain.rpcTarget);
- });
+ finalConfig.chains = [wagmiChains[0], ...wagmiChains.slice(1)];
+ }
+ }
- finalConfig.chains = [wagmiChains[0], ...wagmiChains.slice(1)];
+ if (web3Auth?.coreOptions?.ssr) {
+ finalConfig.storage = createStorage({
+ storage: cookieStorage,
+ });
}
return createWagmiConfig(finalConfig);
@@ -186,7 +185,12 @@ export function WagmiProvider({ children, ...props }: PropsWithChildren {
+ if (!cookie) return undefined;
+ if (wagmiStorageKeyPrefix) {
+ defaultWagmiConfig.storage = {
+ ...defaultWagmiConfig.storage,
+ key: wagmiStorageKeyPrefix,
+ };
+ }
+ return cookieToInitialState(createConfig(defaultWagmiConfig), cookie);
+};