Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/clever-shrimps-do.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@everipedia/iq-login": patch
---

Implements multichain support for web3auth
11 changes: 6 additions & 5 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ export { Login } from "./components/login-element";
// ===============
// Hooks
// ===============
export { useAuth } from "./lib/hooks/use-auth";
export { useAuth } from "./hooks/use-auth";
export { useWeb3Auth } from "./hooks/use-web-3-auth";

// ===============
// Config
// ===============
export {
getWagmiConfig,
createWeb3AuthInstance,
} from "./lib/integrations/wagmi.config";
export { iqTestnet } from "./lib/data/iq-testnet";
createIqLoginConfig,
type IqLoginConfig,
} from "./config/iq-login.config";
export { iqTestnet } from "./config/iq-testnet.config";
32 changes: 13 additions & 19 deletions src/components/iq-login-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,26 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { structuralSharing } from "@wagmi/core/query";
import type React from "react";
import { createContext, useMemo } from "react";
import { cookieToInitialState, WagmiProvider, type Config } from "wagmi";
import { createWeb3AuthInstance } from "../lib/integrations/wagmi.config";
import { Web3AuthProvider } from "./web3-auth-provider";
import { useMemo } from "react";
import { cookieToInitialState, WagmiProvider } from "wagmi";
import type { IqLoginConfig } from "../config/iq-login.config";
import { ProjectContext } from "../hooks/use-project";
import { Web3AuthProvider } from "../hooks/use-web-3-auth";

interface IqLoginProviderProps {
projectName: string;
cookie?: string | null;
wagmiConfig: Config;
config: IqLoginConfig;
disableAuth?: boolean;
}

export const ProjectContext = createContext<{
projectName: string;
disableAuth: boolean;
}>({ projectName: "", disableAuth: false });

export function IqLoginProvider({
children,
cookie,
projectName,
disableAuth = false,
wagmiConfig,
config,
}: React.PropsWithChildren<IqLoginProviderProps>) {
const web3AuthInstance = useMemo(() => {
const primaryChain = Object.values(wagmiConfig.chains)[0];
return createWeb3AuthInstance(primaryChain);
}, [wagmiConfig]);

const queryClient = useMemo(
() =>
new QueryClient({
Expand All @@ -45,13 +36,16 @@ export function IqLoginProvider({
[],
);

const initialState = cookieToInitialState(wagmiConfig, cookie);
const initialState = cookieToInitialState(config.wagmiConfig, cookie);

return (
<ProjectContext.Provider value={{ projectName, disableAuth }}>
<WagmiProvider config={wagmiConfig} initialState={initialState}>
<WagmiProvider config={config.wagmiConfig} initialState={initialState}>
<QueryClientProvider client={queryClient}>
<Web3AuthProvider web3AuthInstance={web3AuthInstance}>
<Web3AuthProvider
web3AuthInstance={config.web3AuthInstance}
chains={config.chains}
>
{children}
</Web3AuthProvider>
</QueryClientProvider>
Expand Down
12 changes: 6 additions & 6 deletions src/components/login-element.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import {
Wallet,
XCircle,
} from "lucide-react";
import { useContext, useEffect } from "react";
import { useEffect } from "react";
import { useAccount, useConnect } from "wagmi";
import { useAuth } from "../client";
import { Injected } from "../lib/icons/injected";
import { Social } from "../lib/icons/social";
import { WalletConnect } from "../lib/icons/wallet-connect";
import { ProjectContext } from "./iq-login-provider";
import { useProject } from "../hooks/use-project";
import { Injected } from "../icons/injected";
import { Social } from "../icons/social";
import { WalletConnect } from "../icons/wallet-connect";

export const Login = ({
title = "Welcome Back",
Expand All @@ -29,7 +29,7 @@ export const Login = ({
handleRedirect?: () => void;
}) => {
const { isConnected } = useAccount();
const { disableAuth } = useContext(ProjectContext);
const { disableAuth } = useProject();

return (
<div className="min-h-[60vh] w-full flex items-center justify-center px-4 py-8">
Expand Down
38 changes: 0 additions & 38 deletions src/components/web3-auth-provider.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -29,40 +29,22 @@ if (!WEB_3_AUTH_CLIENT_ID) {
);
}

export const createWeb3AuthInstance = (chain: Chain) => {
const chainConfig = {
chainNamespace: Web3AuthBase.CHAIN_NAMESPACES.EIP155,
chainId: `0x${chain.id.toString(16)}`,
rpcTarget: chain.rpcUrls.default.http[0],
displayName: chain.name,
tickerName: chain.nativeCurrency?.name,
ticker: chain.nativeCurrency?.symbol,
blockExplorerUrl: chain.blockExplorers?.default.url[0] as string,
};

return new Web3AuthModal.Web3Auth({
clientId: WEB_3_AUTH_CLIENT_ID,
privateKeyProvider: new Web3AuthEthereumProvider.EthereumPrivateKeyProvider(
{
config: { chainConfig },
},
),
web3AuthNetwork: Web3AuthBase.WEB3AUTH_NETWORK.SAPPHIRE_MAINNET,
});
};
export interface IqLoginConfig {
wagmiConfig: Config;
web3AuthInstance: Web3AuthModal.Web3Auth;
chains: [Chain, ...Chain[]];
}

export function getWagmiConfig(
export function createIqLoginConfig(
chains: [Chain, ...Chain[]] = [mainnet],
): Config {
): IqLoginConfig {
const transports = Object.fromEntries(
chains.map((chain) => [chain.id, http()]),
);

// TODO: checkout https://web3auth.io/community/t/how-to-switchchain-using-wagmi-connectors/6488
// to implement proper chain switching to web3auth
const web3AuthInstance = createWeb3AuthInstance(chains[0]);

return createConfig({
const wagmiConfig = createConfig({
chains,
transports,
connectors: [
Expand All @@ -84,4 +66,34 @@ export function getWagmiConfig(
ssr: true,
multiInjectedProviderDiscovery: false,
});

return {
wagmiConfig,
web3AuthInstance,
chains,
};
}

function createWeb3AuthInstance(defaultChain: Chain) {
// Create the default chain config
const chainConfig = {
chainNamespace: Web3AuthBase.CHAIN_NAMESPACES.EIP155,
chainId: `0x${defaultChain.id.toString(16)}`,
rpcTarget: defaultChain.rpcUrls.default.http[0],
displayName: defaultChain.name,
tickerName: defaultChain.nativeCurrency?.name,
ticker: defaultChain.nativeCurrency?.symbol,
blockExplorerUrl: defaultChain.blockExplorers?.default.url[0] as string,
};

// Initialize Web3Auth with only the default chain
return new Web3AuthModal.Web3Auth({
clientId: WEB_3_AUTH_CLIENT_ID,
privateKeyProvider: new Web3AuthEthereumProvider.EthereumPrivateKeyProvider(
{
config: { chainConfig },
},
),
web3AuthNetwork: Web3AuthBase.WEB3AUTH_NETWORK.SAPPHIRE_MAINNET,
});
}
File renamed without changes.
18 changes: 9 additions & 9 deletions src/lib/hooks/use-auth.ts → src/hooks/use-auth.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { useDisconnect, useWalletClient } from "wagmi";
import { sign, verify } from "@everipedia/web3-signer";
import { create } from "zustand";
import { getCookie, deleteCookie, setCookie } from "cookies-next";
import { useWeb3Auth } from "../../components/web3-auth-provider";
import type { UserInfo } from "@web3auth/base";
import { useMutation } from "@tanstack/react-query";
import type { GetWalletClientReturnType } from "@wagmi/core";
import { useContext, useEffect } from "react";
import { AUTH_TOKEN_KEY } from "../constants";
import { ProjectContext } from "../../components/iq-login-provider";
import type { UserInfo } from "@web3auth/base";
import { deleteCookie, getCookie, setCookie } from "cookies-next";
import { useEffect } from "react";
import { useDisconnect, useWalletClient } from "wagmi";
import { create } from "zustand";
import { useWeb3Auth } from "./use-web-3-auth";
import { AUTH_TOKEN_KEY } from "../server/constants";
import { useProject } from "./use-project";

export const useTokenStore = create<{
token: string | null;
Expand All @@ -22,7 +22,7 @@ export const useAuth = () => {
const { token, setToken } = useTokenStore((state) => state);
const { data: walletClient } = useWalletClient();
const { user: web3AuthUser } = useWeb3Auth();
const { projectName, disableAuth } = useContext(ProjectContext);
const { disableAuth, projectName } = useProject();

const disconnectMutation = useDisconnect({
mutation: {
Expand Down
15 changes: 15 additions & 0 deletions src/hooks/use-project.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { createContext, useContext } from "react";

export interface ProjectContextType {
projectName: string;
disableAuth: boolean;
}

export const ProjectContext = createContext<ProjectContextType>({
projectName: "",
disableAuth: false,
});

export const useProject = () => {
return useContext(ProjectContext);
};
76 changes: 76 additions & 0 deletions src/hooks/use-web-3-auth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import type { UserInfo } from "@web3auth/base";
import { CHAIN_NAMESPACES } from "@web3auth/base";
import type { Web3Auth } from "@web3auth/modal";
import { createContext, useContext, useEffect, useState } from "react";
import type { Chain } from "viem/chains";

export interface Web3AuthContextType {
web3Auth: Web3Auth | null;
user: Partial<UserInfo> | null;
chains: Chain[];
}

const Web3AuthContext = createContext<Web3AuthContextType>({
web3Auth: null,
user: null,
chains: [],
});

export function Web3AuthProvider({
children,
web3AuthInstance,
chains,
}: {
children: React.ReactNode;
web3AuthInstance: Web3Auth;
chains: Chain[];
}) {
const [user, setUser] = useState<Partial<UserInfo> | null>(null);
const [chainsAdded, setChainsAdded] = useState(false);

useEffect(() => {
web3AuthInstance.on("connected", async () => {
setUser(await web3AuthInstance.getUserInfo());

// Add additional chains after the wallet is connected
if (!chainsAdded && chains.length > 1) {
try {
// Skip the first chain as it's already configured
for (let i = 1; i < chains.length; i++) {
const chain = chains[i];
const chainConfig = {
chainNamespace: CHAIN_NAMESPACES.EIP155,
chainId: `0x${chain.id.toString(16)}`,
rpcTarget: chain.rpcUrls.default.http[0],
displayName: chain.name,
tickerName: chain.nativeCurrency?.name,
ticker: chain.nativeCurrency?.symbol,
blockExplorerUrl: chain.blockExplorers?.default.url[0] as string,
};

await web3AuthInstance.addChain(chainConfig);
}
setChainsAdded(true);
} catch (error) {
console.error("Failed to add chains:", error);
}
}
});

web3AuthInstance.on("disconnected", () => {
setUser(null);
});
}, [web3AuthInstance, chains, chainsAdded]);

return (
<Web3AuthContext.Provider
value={{ web3Auth: web3AuthInstance, user, chains }}
>
{children}
</Web3AuthContext.Provider>
);
}

export function useWeb3Auth() {
return useContext(Web3AuthContext);
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
6 changes: 3 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
// ===============
// Helpers
// ===============
export { getAuth } from "./lib/getAuth";
export { getAuth } from "./server/get-auth";

// ===============
// Data
// ===============
export { iqTestnet } from "./lib/data/iq-testnet";
export { iqTestnet } from "./config/iq-testnet.config";

// ===============
// Constants
// ===============
export { AUTH_TOKEN_KEY } from "./lib/constants";
export { AUTH_TOKEN_KEY } from "./server/constants";
Loading
Loading