From 35e0a640f39bfb150aeeb1dc55da773f2a4a9699 Mon Sep 17 00:00:00 2001 From: lwin Date: Wed, 27 May 2026 17:55:37 +0800 Subject: [PATCH 01/10] chore: refactor account linking errors --- .../no-modal/src/account-linking/errors.ts | 73 +++++++++++++++++++ .../no-modal/src/account-linking/index.ts | 1 + packages/no-modal/src/account-linking/rest.ts | 6 +- packages/no-modal/src/base/errors/index.ts | 55 -------------- .../auth-connector/authConnector.ts | 2 +- packages/no-modal/src/noModal.ts | 13 +++- 6 files changed, 88 insertions(+), 62 deletions(-) create mode 100644 packages/no-modal/src/account-linking/errors.ts diff --git a/packages/no-modal/src/account-linking/errors.ts b/packages/no-modal/src/account-linking/errors.ts new file mode 100644 index 000000000..73914989d --- /dev/null +++ b/packages/no-modal/src/account-linking/errors.ts @@ -0,0 +1,73 @@ +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); + } +} + +export async function getAccountLinkingRequestError(error: unknown): Promise { + 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") { + const json = await error.json(); + return AccountLinkingError.requestFailed(json.message ?? "Failed to link account"); + } + } + if (error instanceof AccountLinkingError) { + return error; + } + return AccountLinkingError.requestFailed(error instanceof Error ? error.message : JSON.stringify(error), error); +} diff --git a/packages/no-modal/src/account-linking/index.ts b/packages/no-modal/src/account-linking/index.ts index 95db5c73f..bb6c0e91a 100644 --- a/packages/no-modal/src/account-linking/index.ts +++ b/packages/no-modal/src/account-linking/index.ts @@ -1,2 +1,3 @@ +export * from "./errors"; export * from "./interfaces"; export * from "./rest"; diff --git a/packages/no-modal/src/account-linking/rest.ts b/packages/no-modal/src/account-linking/rest.ts index f69bb884e..8eafb8649 100644 --- a/packages/no-modal/src/account-linking/rest.ts +++ b/packages/no-modal/src/account-linking/rest.ts @@ -1,6 +1,6 @@ import { post } from "@toruslabs/http-helpers"; -import { AccountLinkingError } from "../base/errors"; +import { AccountLinkingError, getAccountLinkingRequestError } from "./errors"; import { CitadelLinkAccountPayload, LinkAccountResult, UnlinkAccountPayload, UnlinkAccountResult } from "./interfaces"; /** @@ -24,8 +24,8 @@ export async function makeAccountLinkingRequest( }, }); } catch (cause: unknown) { - const message = cause instanceof Error ? cause.message : String(cause); - throw AccountLinkingError.requestFailed(message, cause); + const accountLinkingError = await getAccountLinkingRequestError(cause); + throw accountLinkingError; } if (!result.success) { diff --git a/packages/no-modal/src/base/errors/index.ts b/packages/no-modal/src/base/errors/index.ts index d58507641..55ba711e9 100644 --- a/packages/no-modal/src/base/errors/index.ts +++ b/packages/no-modal/src/base/errors/index.ts @@ -228,61 +228,6 @@ export class WalletOperationsError extends Web3AuthError { } } -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); - } -} - export class WalletProviderError extends Web3AuthError { protected static messages: ErrorCodes = { 5000: "Custom", diff --git a/packages/no-modal/src/connectors/auth-connector/authConnector.ts b/packages/no-modal/src/connectors/auth-connector/authConnector.ts index d19de08d8..58c19c645 100644 --- a/packages/no-modal/src/connectors/auth-connector/authConnector.ts +++ b/packages/no-modal/src/connectors/auth-connector/authConnector.ts @@ -26,6 +26,7 @@ import deepmerge from "deepmerge"; import { numberToHex } from "viem"; import { + AccountLinkingError, CITADEL_NETWORK, LinkAccountResult, makeAccountLinkingRequest, @@ -33,7 +34,6 @@ import { UnlinkAccountResult, } from "../../account-linking"; import { - AccountLinkingError, Analytics, ANALYTICS_EVENTS, AuthLoginParams, diff --git a/packages/no-modal/src/noModal.ts b/packages/no-modal/src/noModal.ts index 1605ace01..2c11e4ecb 100644 --- a/packages/no-modal/src/noModal.ts +++ b/packages/no-modal/src/noModal.ts @@ -19,7 +19,7 @@ import { import { WsEmbedParams } from "@web3auth/ws-embed"; import deepmerge from "deepmerge"; -import { type LinkAccountParams, type LinkAccountResult, UnlinkAccountResult } from "./account-linking"; +import { AccountLinkingError, type LinkAccountParams, type LinkAccountResult, UnlinkAccountResult } from "./account-linking"; import { Analytics, ANALYTICS_EVENTS, @@ -85,7 +85,6 @@ import { withAbort, } from "./base"; import { deserialize } from "./base/deserialize"; -import { AccountLinkingError } from "./base/errors"; import { assertAuthConnector, authConnector, @@ -1401,7 +1400,15 @@ export class Web3AuthNoModal extends SafeEventEmitter imp chainId: string, config?: ProjectConfig ): Promise> { - return this.createIsolatedWalletConnector(connectorName, chainId, config); + try { + const linkingConnector = await this.createIsolatedWalletConnector(connectorName, chainId, config); + return linkingConnector; + } catch (error) { + if (error instanceof AccountLinkingError && error.code === 5405) { + throw error; + } + throw AccountLinkingError.walletProofFailed(error instanceof Error ? error.message : String(error), error); + } } protected async createSwitchingWalletConnector( From f91e514423f4e04dba88ddf9807b57755c502632 Mon Sep 17 00:00:00 2001 From: lwin Date: Wed, 27 May 2026 20:57:30 +0800 Subject: [PATCH 02/10] feat: handle acount linking errors and format to Proper error message --- packages/modal/src/modalManager.ts | 16 ++++---------- .../no-modal/src/account-linking/errors.ts | 21 +++++++++++++++++++ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/packages/modal/src/modalManager.ts b/packages/modal/src/modalManager.ts index 93c2ae2c5..98fa2fd32 100644 --- a/packages/modal/src/modalManager.ts +++ b/packages/modal/src/modalManager.ts @@ -1,6 +1,5 @@ import { AuthConnectionConfigItem, serializeError } from "@web3auth/auth"; import { - AccountLinkingError, ANALYTICS_EVENTS, ANALYTICS_SDK_TYPE, type AUTH_CONNECTION_TYPE, @@ -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"; @@ -381,14 +381,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> { const { projectConfig } = await this.getProjectAndWalletConfig(); const connector = await super.createLinkingWalletConnector(connectorName, chainId, projectConfig); @@ -1046,7 +1038,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 }); } @@ -1149,9 +1141,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) { diff --git a/packages/no-modal/src/account-linking/errors.ts b/packages/no-modal/src/account-linking/errors.ts index 73914989d..661c8bae5 100644 --- a/packages/no-modal/src/account-linking/errors.ts +++ b/packages/no-modal/src/account-linking/errors.ts @@ -53,6 +53,10 @@ export class AccountLinkingError extends Web3AuthError { public static cannotUnlinkPrimaryAccount(): AccountLinkingError { return AccountLinkingError.fromCode(5408); } + + public toString(): string { + return `[${this.code}] ${this.message}`; + } } export async function getAccountLinkingRequestError(error: unknown): Promise { @@ -71,3 +75,20 @@ export async function getAccountLinkingRequestError(error: unknown): Promise Date: Wed, 27 May 2026 22:09:57 +0800 Subject: [PATCH 03/10] feat: handle user rejected error --- .../src/components/AccountLinkingSection.vue | 19 +++++++++++----- packages/no-modal/src/account-linking/vue.ts | 2 +- packages/no-modal/src/base/errors/index.ts | 22 ++++++++++++++++++- .../auth-connector/authConnector.ts | 4 +--- .../metamask-connector/metamaskConnector.ts | 4 ++++ 5 files changed, 40 insertions(+), 11 deletions(-) diff --git a/demo/vue-app-new/src/components/AccountLinkingSection.vue b/demo/vue-app-new/src/components/AccountLinkingSection.vue index c865b4bbc..1494aae15 100644 --- a/demo/vue-app-new/src/components/AccountLinkingSection.vue +++ b/demo/vue-app-new/src/components/AccountLinkingSection.vue @@ -1,6 +1,6 @@