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
6 changes: 3 additions & 3 deletions demo/vue-app-new/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 1 addition & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 6 additions & 12 deletions packages/modal/src/modalManager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { AuthConnectionConfigItem, serializeError } from "@web3auth/auth";
import {
AccountLinkingError,
ANALYTICS_EVENTS,
ANALYTICS_SDK_TYPE,
type AUTH_CONNECTION_TYPE,
Expand Down Expand Up @@ -47,6 +46,7 @@ import {
Web3AuthNoModal,
withAbort,
} from "@web3auth/no-modal";
import { AccountLinkingError, formatAccountLinkingErrorMessage } from "@web3auth/no-modal/account-linking";
import deepmerge from "deepmerge";

import { defaultConnectorsModalConfig } from "./config";
Expand Down Expand Up @@ -133,6 +133,8 @@ export class Web3Auth extends Web3AuthNoModal implements IWeb3AuthModal {
super.checkInitRequirements();
// get project config and wallet registry
const { projectConfig, walletRegistry } = await this.getProjectAndWalletConfig();
// cache project config so that it can be re-used later
this.projectConfig = projectConfig;

// init config
this.initUIConfig(projectConfig);
Expand Down Expand Up @@ -381,14 +383,6 @@ export class Web3Auth extends Web3AuthNoModal implements IWeb3AuthModal {
this.loginModal.resetAccountLinkingSession();
}

protected formatAccountLinkingErrorMessage(error: unknown): string | undefined {
if (error instanceof AccountLinkingError) {
const isUnlink = error.code >= 5406 && error.code <= 5408;
return isUnlink ? `[${error.code}] Account unlinking failed` : `[${error.code}] Account linking failed`;
}
return (error as Error)?.message;
}

protected async prepareAccountLinkingConnector(connectorName: WALLET_CONNECTOR_TYPE | string, chainId: string): Promise<IConnector<unknown>> {
const { projectConfig } = await this.getProjectAndWalletConfig();
const connector = await super.createLinkingWalletConnector(connectorName, chainId, projectConfig);
Expand Down Expand Up @@ -1046,7 +1040,7 @@ export class Web3Auth extends Web3AuthNoModal implements IWeb3AuthModal {
this.resetAccountLinkingModalSession();
} else {
const fallbackMessage = params.intent === ACCOUNT_LINKING_INTENT.SWITCH ? "Failed to switch wallet." : undefined;
const errorMessage = this.formatAccountLinkingErrorMessage(error) || fallbackMessage;
const errorMessage = formatAccountLinkingErrorMessage(error, fallbackMessage);
this.resetAccountLinkingModalSession();
this.loginModal.endConnectingLoader({ success: false, errorMessage });
}
Expand Down Expand Up @@ -1149,9 +1143,9 @@ export class Web3Auth extends Web3AuthNoModal implements IWeb3AuthModal {
this.loginModal.endConnectingLoader({ success: true, skipSuccessScreen: options.skipSuccessScreen });
return result;
} catch (error) {
const message = this.formatAccountLinkingErrorMessage(error);
const errorMessage = formatAccountLinkingErrorMessage(error);
this.resetAccountLinkingModalSession();
this.loginModal.endConnectingLoader({ success: false, errorMessage: message });
this.loginModal.endConnectingLoader({ success: false, errorMessage });
throw error;
} finally {
if (options.connector) {
Expand Down
60 changes: 32 additions & 28 deletions packages/modal/src/react/wagmi/provider.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CHAIN_NAMESPACES, type CustomChainConfig, log, WalletInitializationError } from "@web3auth/no-modal";
import { CHAIN_NAMESPACES, type CustomChainConfig, IProvider, log, WalletInitializationError } from "@web3auth/no-modal";
import {
connectWeb3AuthWithWagmi,
disconnectWeb3AuthFromWagmi,
Expand All @@ -24,17 +24,13 @@ import { WagmiProviderProps } from "./interface";

// TODO: re-use the provider from the no-modal package
function Web3AuthWagmiProvider({ children }: PropsWithChildren) {
const {
isConnected,
connection,
chainNamespace,
web3Auth: { primaryConnectorName },
} = useWeb3Auth();
const { isConnected, connection, chainNamespace } = useWeb3Auth();
const { disconnect } = useWeb3AuthDisconnect();
const wagmiConfig = useWagmiConfig();
const { mutate: reconnect } = useReconnect();
const suppressWagmiDisconnect = useRef(false);
const lastSyncedWeb3AuthConnection = useRef<unknown>(null);
const lastSyncedProvider = useRef<IProvider | null>(connection?.ethereumProvider ?? null);
const lastSyncedConnectorName = useRef<string | null>(connection?.connectorName ?? null);

useConnectionEffect({
onDisconnect: async () => {
Expand All @@ -56,31 +52,39 @@ function Web3AuthWagmiProvider({ children }: PropsWithChildren) {
const newConnection = connection ?? null;
const newEth = connection?.ethereumProvider ?? null;
const w3aWagmiConnector = getWeb3authConnector(wagmiConfig);
const shouldBindToWagmi =
isConnected &&
chainNamespace === CHAIN_NAMESPACES.EIP155 &&
Boolean(newConnection && newEth) &&
newConnection?.connectorName === primaryConnectorName;
const shouldBindToWagmi = isConnected && chainNamespace === CHAIN_NAMESPACES.EIP155 && Boolean(newConnection && newEth);

if (shouldBindToWagmi) {
const hasSameBinding =
lastSyncedProvider.current === newEth &&
lastSyncedConnectorName.current === newConnection.connectorName &&
wagmiConfig.state.status === "connected";

if (hasSameBinding) {
// rehydration: already connected to the same provider, so no need to reconnect
return;
}

// `ethereumProvider` is a stable proxy (`commonJRPCProvider`) across account switches,
// so key wagmi resyncs off the Web3Auth connection object instead of provider identity.
if (lastSyncedWeb3AuthConnection.current !== newConnection) {
if (w3aWagmiConnector) {
resetConnectorState(wagmiConfig);
}
lastSyncedWeb3AuthConnection.current = newConnection;
const connector = setupConnector(newEth, wagmiConfig);
if (!connector) {
log.error("Failed to setup react wagmi connector");
throw new Error("Failed to setup connector");
}

await connectWeb3AuthWithWagmi(connector, wagmiConfig);
reconnect();
if (w3aWagmiConnector) {
resetConnectorState(wagmiConfig);
}

lastSyncedProvider.current = newEth;
lastSyncedConnectorName.current = newConnection.connectorName;

const connector = setupConnector(newEth, wagmiConfig);
if (!connector) {
log.error("Failed to setup react wagmi connector");
throw new Error("Failed to setup connector");
}

await connectWeb3AuthWithWagmi(connector, wagmiConfig);
reconnect();
} else if (!isConnected || chainNamespace !== CHAIN_NAMESPACES.EIP155) {
lastSyncedWeb3AuthConnection.current = null;
lastSyncedProvider.current = null;
lastSyncedConnectorName.current = null;
if (wagmiConfig.state.status === "connected") {
suppressWagmiDisconnect.current = true;
await disconnectWeb3AuthFromWagmi(wagmiConfig);
Expand All @@ -89,7 +93,7 @@ function Web3AuthWagmiProvider({ children }: PropsWithChildren) {
}
}
})();
}, [chainNamespace, isConnected, wagmiConfig, connection, reconnect, primaryConnectorName]);
}, [chainNamespace, isConnected, wagmiConfig, connection, reconnect]);

return createElement(Fragment, null, children);
}
Expand Down
55 changes: 32 additions & 23 deletions packages/modal/src/vue/wagmi/provider.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Config, CreateConfigParameters, hydrate } from "@wagmi/core";
import { configKey, createConfig as createWagmiConfig, useConfig as useWagmiConfig, useConnectionEffect, useReconnect } from "@wagmi/vue";
import { randomId } from "@web3auth/auth";
import { CHAIN_NAMESPACES, type CustomChainConfig, log, WalletInitializationError } from "@web3auth/no-modal";
import { CHAIN_NAMESPACES, type CustomChainConfig, IProvider, log, WalletInitializationError } from "@web3auth/no-modal";
import {
connectWeb3AuthWithWagmi,
disconnectWeb3AuthFromWagmi,
Expand All @@ -21,11 +21,12 @@ import { WagmiProviderProps } from "./interface";
const Web3AuthWagmiProvider = defineComponent({
name: "Web3AuthWagmiProvider",
setup() {
const { isConnected, connection, web3Auth, chainNamespace } = useWeb3Auth();
const { isConnected, connection, chainNamespace, web3Auth } = useWeb3Auth();
const { disconnect } = useWeb3AuthDisconnect();
const wagmiConfig = useWagmiConfig();
const { mutate: reconnect } = useReconnect();
const lastSyncedWeb3AuthConnection = shallowRef<unknown>(null);
const lastSyncedProvider = shallowRef<IProvider | null>(connection.value?.ethereumProvider ?? null);
const lastSyncedConnectorName = ref<string | null>(connection.value?.connectorName ?? null);
const suppressWagmiDisconnect = ref(false);

useConnectionEffect({
Expand All @@ -52,31 +53,39 @@ const Web3AuthWagmiProvider = defineComponent({
const newEth = newConnection?.ethereumProvider ?? null;
const w3aWagmiConnector = getWeb3authConnector(wagmiConfig);

const shouldBindToWagmi =
newIsConnected &&
chainNamespace.value === CHAIN_NAMESPACES.EIP155 &&
Boolean(newConnection && newEth) &&
newConnection?.connectorName === web3Auth.value?.primaryConnectorName;
const shouldBindToWagmi = newIsConnected && chainNamespace.value === CHAIN_NAMESPACES.EIP155 && Boolean(newConnection && newEth);

if (shouldBindToWagmi) {
const hasSameBinding =
lastSyncedProvider.value === newEth &&
lastSyncedConnectorName.value === newConnection.connectorName &&
newConnection?.connectorName === web3Auth.value?.connection.connectorName &&
wagmiConfig.state.status === "connected";

if (hasSameBinding) {
// rehydration: already connected to the same provider, so no need to reconnect
return;
}

if (shouldBindToWagmi && newConnection && newEth) {
// `ethereumProvider` is a stable proxy (`commonJRPCProvider`) across account switches,
// so key wagmi resyncs off the Web3Auth connection object instead of provider identity.
if (lastSyncedWeb3AuthConnection.value !== newConnection) {
if (w3aWagmiConnector) {
resetConnectorState(wagmiConfig);
}
lastSyncedWeb3AuthConnection.value = newConnection;
const connector = setupConnector(newEth, wagmiConfig);
if (!connector) {
log.error("Failed to setup vue wagmi connector");
throw new Error("Failed to setup connector");
}

await connectWeb3AuthWithWagmi(connector, wagmiConfig);
reconnect();
if (w3aWagmiConnector) {
resetConnectorState(wagmiConfig);
}

lastSyncedProvider.value = newEth;
lastSyncedConnectorName.value = newConnection.connectorName;
const connector = setupConnector(newEth, wagmiConfig);
if (!connector) {
log.error("Failed to setup vue wagmi connector");
throw new Error("Failed to setup connector");
}

await connectWeb3AuthWithWagmi(connector, wagmiConfig);
reconnect();
} else if (!newIsConnected || chainNamespace.value !== CHAIN_NAMESPACES.EIP155) {
lastSyncedWeb3AuthConnection.value = null;
lastSyncedProvider.value = null;
lastSyncedConnectorName.value = null;
if (wagmiConfig.state.status === "connected") {
suppressWagmiDisconnect.value = true;
await disconnectWeb3AuthFromWagmi(wagmiConfig);
Expand Down
100 changes: 100 additions & 0 deletions packages/no-modal/src/account-linking/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { ErrorCodes, Web3AuthError } from "../base";

export class AccountLinkingError extends Web3AuthError {
protected static messages: ErrorCodes = {
5000: "Custom",
5401: "Account linking request failed",
5402: "Citadel server URL is not configured",
5403: "Primary identity token is not available",
5404: "Failed to obtain wallet proof token",
5405: "Connector is not supported for wallet linking",
5406: "Cannot unlink active account",
5407: "Account not linked",
5408: "Cannot unlink primary account",
};

public constructor(code: number, message?: string, cause?: unknown) {
super(code, message, cause);
Object.defineProperty(this, "name", { value: "AccountLinkingError", configurable: true });
}

public static fromCode(code: number, extraMessage = "", cause?: unknown): AccountLinkingError {
return new AccountLinkingError(code, `${AccountLinkingError.messages[code]}. ${extraMessage}`, cause);
}

public static requestFailed(extraMessage = "", cause?: unknown): AccountLinkingError {
return AccountLinkingError.fromCode(5401, extraMessage, cause);
}

public static serverNotConfigured(extraMessage = "", cause?: unknown): AccountLinkingError {
return AccountLinkingError.fromCode(5402, extraMessage, cause);
}

public static primaryTokenNotAvailable(extraMessage = "", cause?: unknown): AccountLinkingError {
return AccountLinkingError.fromCode(5403, extraMessage, cause);
}

public static walletProofFailed(extraMessage = "", cause?: unknown): AccountLinkingError {
return AccountLinkingError.fromCode(5404, extraMessage, cause);
}

public static unsupportedConnector(extraMessage = "", cause?: unknown): AccountLinkingError {
return AccountLinkingError.fromCode(5405, extraMessage, cause);
}

public static cannotUnlinkActiveAccount(): AccountLinkingError {
return AccountLinkingError.fromCode(5406);
}

public static accountNotLinked(message = "", cause?: unknown): AccountLinkingError {
return AccountLinkingError.fromCode(5407, message, cause);
}

public static cannotUnlinkPrimaryAccount(): AccountLinkingError {
return AccountLinkingError.fromCode(5408);
}

public toString(): string {
return `[${this.code}] ${this.message}`;
}
}

export async function getAccountLinkingRequestError(error: unknown): Promise<AccountLinkingError> {
if (error instanceof AccountLinkingError) {
return error;
}

if (error instanceof Response) {
if (error.status === 409) {
return AccountLinkingError.requestFailed("This wallet address is already registered on this dApp");
}

if (error.json && typeof error.json === "function") {
try {
const json = await error.json();
return AccountLinkingError.requestFailed(json.message ?? "Failed to link account");
} catch {
// continue
}
}
Comment thread
cursor[bot] marked this conversation as resolved.
}

return AccountLinkingError.requestFailed(error instanceof Error ? error.message : JSON.stringify(error), error);
}

export function formatAccountLinkingErrorMessage(error: unknown, fallbackMessage: string = "Unknown error during the operation."): string {
if (error instanceof AccountLinkingError) {
return error.toString();
}

if (error instanceof Error) {
return error.message || fallbackMessage;
}

try {
const stringifiedError = JSON.stringify(error);
return stringifiedError;
} catch {
return fallbackMessage;
}
}
1 change: 1 addition & 0 deletions packages/no-modal/src/account-linking/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./errors";
export * from "./interfaces";
export * from "./rest";
Loading
Loading