Skip to content

Commit 16b2178

Browse files
Merge pull request #2303 from Web3Auth/fix/multichain-hooks
Fix solana hooks in a multichain dapp mode.
2 parents 8301d1e + 3621fed commit 16b2178

File tree

19 files changed

+287
-46
lines changed

19 files changed

+287
-46
lines changed

demo/wagmi-react-app/package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

demo/wagmi-react-app/src/components/Main.tsx

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
1+
import { CHAIN_NAMESPACES } from "@web3auth/modal";
12
import {
3+
useChain,
24
useCheckout,
35
useEnableMFA,
46
useIdentityToken,
57
useManageMFA,
8+
useSwitchChain as useWeb3AuthSwitchChain,
69
useWalletConnectScanner,
710
useWalletUI,
811
useWeb3Auth,
912
useWeb3AuthConnect,
1013
useWeb3AuthDisconnect,
1114
useWeb3AuthUser,
1215
} from "@web3auth/modal/react";
16+
import { useSolanaWallet } from "@web3auth/modal/react/solana";
17+
import { useMemo } from "react";
1318
import { useAccount, useBalance, useChainId, useSignMessage, useSignTypedData, useSwitchChain } from "wagmi";
1419

1520
import styles from "../styles/Home.module.css";
1621

1722
const Main = () => {
18-
const { provider, isConnected } = useWeb3Auth();
23+
const { provider, isConnected, web3Auth, status } = useWeb3Auth();
24+
const { accounts: solanaAccounts } = useSolanaWallet();
25+
const { chainNamespace: currentChainNamespace, chainId: currentChainId } = useChain();
1926
const { loading: connecting, connect, error: connectingError, connectorName, connectTo } = useWeb3AuthConnect();
2027
const { disconnect } = useWeb3AuthDisconnect();
2128
const { signMessageAsync, data: signedMessageData } = useSignMessage();
@@ -30,16 +37,36 @@ const Main = () => {
3037
const { showWalletUI, loading: isWalletUILoading, error: walletUIError } = useWalletUI();
3138
const { token, loading: isUserTokenLoading, error: userTokenError, getIdentityToken } = useIdentityToken();
3239
const { switchChainAsync, chains } = useSwitchChain();
40+
const { switchChain: switchWeb3AuthChain } = useWeb3AuthSwitchChain();
41+
3342
const chainId = useChainId();
3443

44+
const chainNamespaces = useMemo(() => {
45+
if (status && web3Auth?.coreOptions?.chains) {
46+
return [...new Set(web3Auth.coreOptions.chains.map((x) => x.chainNamespace) || [])];
47+
}
48+
return [];
49+
}, [status, web3Auth]);
50+
51+
const switchNamespace = (chainNamespace: string) => {
52+
const chainId = web3Auth?.coreOptions.chains?.find((x) => x.chainNamespace === chainNamespace)?.chainId;
53+
if (chainId) {
54+
switchWeb3AuthChain(chainId);
55+
return;
56+
}
57+
throw new Error(`No chain found for the selected namespace: ${chainNamespace}`);
58+
};
59+
3560
const loggedInView = (
3661
<>
3762
<div className={styles.container}>
3863
<div style={{ marginTop: "16px", marginBottom: "16px" }}>
39-
<p>Account Address: {address}</p>
64+
<p>Account Address: {currentChainNamespace === CHAIN_NAMESPACES.EIP155 ? address : solanaAccounts?.[0]}</p>
4065
<p>Account Balance: {balance?.value}</p>
4166
<p>MFA Enabled: {isMFAEnabled ? "Yes" : "No"}</p>
42-
<p>ConnectedChain ID: {chainId}</p>
67+
<p>Wagmi ConnectedChain ID: {chainId}</p>
68+
<p>Web3Auth ConnectedChain ID: {currentChainId}</p>
69+
<p>Web3Auth ConnectedChain Namespace: {currentChainNamespace}</p>
4370
</div>
4471

4572
{/* User Info */}
@@ -194,6 +221,22 @@ const Main = () => {
194221
))}
195222
</div>
196223

224+
<div style={{ marginTop: "16px", marginBottom: "16px" }}>
225+
<p>Chain Namespace</p>
226+
227+
{chainNamespaces.map((namespace) => (
228+
<button
229+
key={namespace}
230+
onClick={async () => switchNamespace(namespace)}
231+
className={styles.card}
232+
disabled={namespace === currentChainNamespace}
233+
style={{ opacity: namespace === currentChainNamespace ? 0.5 : 1 }}
234+
>
235+
Switch to {namespace === CHAIN_NAMESPACES.EIP155 ? "EVM" : "Solana"}
236+
</button>
237+
))}
238+
</div>
239+
197240
{/* Disconnect */}
198241
<div style={{ marginTop: "16px", marginBottom: "16px" }}>
199242
<p>Logout</p>

packages/modal/src/react/context/Web3AuthInnerContext.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
ANALYTICS_INTEGRATION_TYPE,
3+
type ChainNamespaceType,
34
type CONNECTED_EVENT_DATA,
45
CONNECTOR_EVENTS,
56
CONNECTOR_STATUS,
@@ -18,6 +19,8 @@ export function Web3AuthInnerProvider(params: PropsWithChildren<Web3AuthProvider
1819
const { children, config, initialState } = params;
1920
const { web3AuthOptions } = config;
2021

22+
const [chainId, setChainId] = useState<string | null>(null);
23+
const [chainNamespace, setChainNamespace] = useState<ChainNamespaceType | null>(null);
2124
const [isInitializing, setIsInitializing] = useState<boolean>(false);
2225
const [initError, setInitError] = useState<Error | null>(null);
2326
const [provider, setProvider] = useState<IProvider | null>(null);
@@ -49,6 +52,8 @@ export function Web3AuthInnerProvider(params: PropsWithChildren<Web3AuthProvider
4952
setIsInitializing(true);
5053
web3Auth.setAnalyticsProperties({ integration_type: ANALYTICS_INTEGRATION_TYPE.REACT_HOOKS });
5154
await web3Auth.init({ signal: controller.signal });
55+
setChainId(web3Auth.currentChainId);
56+
setChainNamespace(web3Auth.currentChain?.chainNamespace);
5257
} catch (error) {
5358
setInitError(error as Error);
5459
} finally {
@@ -63,6 +68,20 @@ export function Web3AuthInnerProvider(params: PropsWithChildren<Web3AuthProvider
6368
};
6469
}, [web3Auth, config]);
6570

71+
useEffect(() => {
72+
const handleChainChange = async (chainId: string) => {
73+
setChainId(chainId);
74+
setChainNamespace(web3Auth?.currentChain?.chainNamespace);
75+
};
76+
77+
if (provider) {
78+
provider.on("chainChanged", handleChainChange);
79+
return () => {
80+
provider.off("chainChanged", handleChainChange);
81+
};
82+
}
83+
}, [web3Auth, provider]);
84+
6685
useEffect(() => {
6786
const notReadyListener = () => setStatus(web3Auth.status);
6887
const readyListener = () => {
@@ -139,10 +158,25 @@ export function Web3AuthInnerProvider(params: PropsWithChildren<Web3AuthProvider
139158
isInitializing,
140159
initError,
141160
isMFAEnabled,
161+
chainId,
162+
chainNamespace,
142163
getPlugin,
143164
setIsMFAEnabled,
144165
};
145-
}, [web3Auth, isConnected, isMFAEnabled, setIsMFAEnabled, isInitialized, provider, status, getPlugin, isInitializing, initError]);
166+
}, [
167+
web3Auth,
168+
isConnected,
169+
isMFAEnabled,
170+
setIsMFAEnabled,
171+
isInitialized,
172+
provider,
173+
status,
174+
getPlugin,
175+
isInitializing,
176+
initError,
177+
chainId,
178+
chainNamespace,
179+
]);
146180

147181
return createElement(Web3AuthInnerContext.Provider, { value }, children);
148182
}

packages/modal/src/react/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export * from "./useChain";
12
export * from "./useCheckout";
23
export * from "./useEnableMFA";
34
export * from "./useFunding";
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { ChainNamespaceType } from "@web3auth/no-modal";
2+
3+
import { useWeb3AuthInner } from "./useWeb3AuthInner";
4+
5+
export type IUseChain = {
6+
chainId: string | null;
7+
chainNamespace: ChainNamespaceType | null;
8+
};
9+
10+
export const useChain = (): IUseChain => {
11+
const { chainId, chainNamespace } = useWeb3AuthInner();
12+
return {
13+
chainId,
14+
chainNamespace,
15+
};
16+
};

packages/modal/src/react/hooks/useWeb3Auth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { IWeb3AuthInnerContext } from "../interfaces";
22
import { useWeb3AuthInner } from "./useWeb3AuthInner";
33

4-
export type IUseWeb3Auth = Omit<IWeb3AuthInnerContext, "isMFAEnabled" | "setIsMFAEnabled">;
4+
export type IUseWeb3Auth = Omit<IWeb3AuthInnerContext, "isMFAEnabled" | "setIsMFAEnabled" | "chainId" | "chainNamespace">;
55

66
export const useWeb3Auth = (): IUseWeb3Auth => {
77
const { initError, isConnected, isInitialized, isInitializing, provider, status, web3Auth, getPlugin } = useWeb3AuthInner();

packages/modal/src/react/solana/hooks/useSolanaWallet.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Connection } from "@solana/web3.js";
22
import { CHAIN_NAMESPACES, SolanaWallet } from "@web3auth/no-modal";
33
import { useEffect, useMemo, useState } from "react";
44

5+
import { useChain } from "../../hooks";
56
import { useWeb3Auth } from "../../hooks/useWeb3Auth";
67

78
export type IUseSolanaWallet = {
@@ -12,21 +13,24 @@ export type IUseSolanaWallet = {
1213

1314
export const useSolanaWallet = (): IUseSolanaWallet => {
1415
const { provider, web3Auth } = useWeb3Auth();
16+
const { chainNamespace } = useChain();
1517
const [accounts, setAccounts] = useState<string[] | null>(null);
1618

1719
const solanaWallet = useMemo(() => {
1820
if (!provider) return null;
21+
if (chainNamespace !== CHAIN_NAMESPACES.SOLANA) return null;
1922
return new SolanaWallet(provider);
20-
}, [provider]);
23+
}, [provider, chainNamespace]);
2124

2225
const connection = useMemo(() => {
23-
if (!web3Auth || !provider) return null;
26+
if (!web3Auth || !provider || chainNamespace !== CHAIN_NAMESPACES.SOLANA) return null;
2427
return new Connection(web3Auth.currentChain.rpcTarget);
25-
}, [web3Auth, provider]);
28+
}, [web3Auth, provider, chainNamespace]);
2629

2730
useEffect(() => {
2831
const init = async () => {
29-
if (!web3Auth?.currentChain?.chainNamespace || web3Auth.currentChain.chainNamespace !== CHAIN_NAMESPACES.SOLANA) {
32+
if (chainNamespace !== CHAIN_NAMESPACES.SOLANA) {
33+
setAccounts(null);
3034
return;
3135
}
3236
if (!solanaWallet) return;
@@ -37,7 +41,7 @@ export const useSolanaWallet = (): IUseSolanaWallet => {
3741
};
3842

3943
if (solanaWallet) init();
40-
}, [solanaWallet, web3Auth]);
44+
}, [solanaWallet, chainNamespace]);
4145

4246
return { solanaWallet, accounts, connection };
4347
};

packages/modal/src/vue/composables/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export * from "./useChain";
12
export * from "./useCheckout";
23
export * from "./useEnableMFA";
34
export * from "./useFunding";
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { ChainNamespaceType } from "@web3auth/no-modal";
2+
import { computed, ComputedRef } from "vue";
3+
4+
import { useWeb3AuthInner } from "./useWeb3AuthInner";
5+
6+
export type IUseChain = {
7+
chainId: ComputedRef<string | null>;
8+
chainNamespace: ComputedRef<ChainNamespaceType | null>;
9+
};
10+
11+
export const useChain = (): IUseChain => {
12+
const context = useWeb3AuthInner();
13+
14+
const chainId = computed(() => {
15+
if (!context.web3Auth.value?.currentChain) return null;
16+
return context.web3Auth.value.currentChain.chainId;
17+
});
18+
19+
const chainNamespace = computed(() => {
20+
if (!context.web3Auth.value?.currentChain) return null;
21+
return context.web3Auth.value.currentChain.chainNamespace;
22+
});
23+
24+
return {
25+
chainId,
26+
chainNamespace,
27+
};
28+
};

packages/modal/src/vue/solana/composables/useSolanaWallet.ts

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { Connection } from "@solana/web3.js";
22
import { CHAIN_NAMESPACES, SolanaWallet } from "@web3auth/no-modal";
3-
import { Ref, ref, ShallowRef, shallowRef, watch } from "vue";
3+
import { computed, Ref, ref, ShallowRef, shallowRef, watch } from "vue";
44

5-
import { useWeb3Auth } from "../../composables";
5+
import { useChain, useWeb3Auth } from "../../composables";
66

77
export type IUseSolanaWallet = {
88
accounts: Ref<string[] | null>;
@@ -12,37 +12,51 @@ export type IUseSolanaWallet = {
1212

1313
export const useSolanaWallet = (): IUseSolanaWallet => {
1414
const { provider, web3Auth } = useWeb3Auth();
15+
const { chainNamespace } = useChain();
1516
const accounts = ref<string[]>([]);
1617
const solanaWallet = shallowRef<SolanaWallet | null>(null);
1718
const connection = shallowRef<Connection | null>(null);
1819

20+
const isSolana = computed(() => chainNamespace.value === CHAIN_NAMESPACES.SOLANA);
21+
1922
const setupWallet = async () => {
20-
if (!web3Auth.value?.currentChain?.chainNamespace || web3Auth.value.currentChain.chainNamespace !== CHAIN_NAMESPACES.SOLANA) {
23+
if (!isSolana.value) {
24+
return;
25+
}
26+
if (!provider.value) {
2127
return;
2228
}
2329
solanaWallet.value = new SolanaWallet(provider.value);
2430
const result = await solanaWallet.value.getAccounts();
2531
if (result?.length > 0) {
2632
accounts.value = result;
2733
}
28-
connection.value = new Connection(web3Auth.value?.currentChain?.rpcTarget);
34+
if (web3Auth.value?.currentChain?.rpcTarget) {
35+
connection.value = new Connection(web3Auth.value.currentChain.rpcTarget);
36+
}
37+
};
38+
39+
const resetWallet = () => {
40+
solanaWallet.value = null;
41+
accounts.value = null;
42+
connection.value = null;
2943
};
3044

3145
if (provider.value && !solanaWallet.value) {
3246
setupWallet();
3347
}
3448

3549
watch(
36-
provider,
37-
async (newVal) => {
38-
if (!newVal && solanaWallet.value) {
39-
solanaWallet.value = null;
40-
accounts.value = null;
41-
connection.value = null;
50+
[provider, chainNamespace],
51+
async ([newProvider, newChainNamespace]) => {
52+
if (!newProvider || newChainNamespace !== CHAIN_NAMESPACES.SOLANA) {
53+
if (solanaWallet.value) {
54+
resetWallet();
55+
}
4256
return;
4357
}
4458

45-
if (newVal && !solanaWallet.value) {
59+
if (newProvider && !solanaWallet.value) {
4660
setupWallet();
4761
}
4862
},

0 commit comments

Comments
 (0)