Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions demo/nextjs-ssr-app/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"] });

Expand All @@ -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 (
<html lang="en">
<body className={inter.className}>
<Provider web3authInitialState={web3authInitialState}>{children}</Provider>
<Provider web3authInitialState={web3authInitialState} wagmiInitialState={wagmiInitialState}>{children}</Provider>
</body>
</html>
);
Expand Down
31 changes: 25 additions & 6 deletions demo/nextjs-ssr-app/components/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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 = (
<>
<div className="container">
<div style={{ marginTop: "16px", marginBottom: "16px" }}>
{/* <p>Account Address: {address}</p> */}
{/* <p>Account Balance: {balance?.value}</p> */}
<p>Account Address: {address}</p>
<p>Account Balance: {balance?.formatted}</p>
<p>MFA Enabled: {isMFAEnabled ? "Yes" : "No"}</p>
<p>ConnectedChain ID: {chainId}</p>
</div>

{/* User Info */}
Expand Down Expand Up @@ -177,6 +180,22 @@ const Main = () => {
{signedTypedDataData && <textarea disabled rows={5} value={signedTypedDataData} style={{ width: "100%" }} />}
</div>

{/* Chain Actions */}
<div style={{ marginTop: "16px", marginBottom: "16px" }}>
<p>Switch Chain</p>
{chains.map((chain) => (
<button
key={chain.id}
onClick={async () => switchChainAsync({ chainId: chain.id })}
className="card"
disabled={chainId === chain.id}
style={{ opacity: chainId === chain.id ? 0.5 : 1 }}
>
Switch to {chain.name}
</button>
))}
</div>

{/* Disconnect */}
<div style={{ marginTop: "16px", marginBottom: "16px" }}>
<p>Logout</p>
Expand Down Expand Up @@ -205,7 +224,7 @@ const Main = () => {
<div className="grid">
<p>Web3Auth: {isConnected ? "Connected" : "Disconnected"}</p>
<p>Wagmi: {isWagmiConnected ? "Connected" : "Disconnected"}</p>
{provider ? loggedInView : unloggedInView}
{isConnected ? loggedInView : unloggedInView}
</div>
);
};
Expand Down
5 changes: 3 additions & 2 deletions demo/nextjs-ssr-app/components/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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 (
<Web3AuthProvider config={web3authConfig} initialState={web3authInitialState}>
<QueryClientProvider client={queryClient}>
<WagmiProvider>
<WagmiProvider initialState={wagmiInitialState}>
{children}
</WagmiProvider>
</QueryClientProvider>
Expand Down
1 change: 1 addition & 0 deletions demo/nextjs-ssr-app/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
},
Expand Down
8 changes: 5 additions & 3 deletions packages/modal/src/react/hooks/useWeb3AuthUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Partial<UserInfo> | null>(null);
const [loading, setLoading] = useState(false);
Expand All @@ -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 };
};
127 changes: 127 additions & 0 deletions packages/modal/src/react/wagmi/connector.ts
Original file line number Diff line number Diff line change
@@ -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<IProvider> {
return createConnector<IProvider>((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<readonly Address[]> {
const provider = await this.getProvider();
return (
await provider.request<unknown, string[]>({
method: "eth_accounts",
})
).map((x: string) => getAddress(x));
},
async getChainId() {
const provider = await this.getProvider();
return Number(provider.chainId);
},
async getProvider(): Promise<IProvider> {
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<Chain> {
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");
},
}));
}
6 changes: 3 additions & 3 deletions packages/modal/src/react/wagmi/constants.ts
Original file line number Diff line number Diff line change
@@ -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]),
},
});
};
1 change: 1 addition & 0 deletions packages/modal/src/react/wagmi/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./interface";
export * from "./provider";
export { cookieToWagmiState } from "./utils";
Loading
Loading