UNPKG

@web3auth/no-modal

Version:
965 lines (940 loc) 97.8 kB
import _objectWithoutProperties from '@babel/runtime/helpers/objectWithoutProperties'; import _objectSpread from '@babel/runtime/helpers/objectSpread2'; import _defineProperty from '@babel/runtime/helpers/defineProperty'; import { CHAIN_NAMESPACES, BUTTON_POSITION, CONFIRMATION_STRATEGY } from '@toruslabs/base-controllers'; import { SMART_ACCOUNT_EIP_STANDARD, EIP7702_SUPPORTED_SMART_ACCOUNT_TYPES } from '@toruslabs/ethereum-controllers'; import { SafeEventEmitter, BUILD_ENV, serializeError, UX_MODE, cloneDeep, CookieStorage, LocalStorageAdapter, MemoryStorage } from '@web3auth/auth'; import deepmerge from 'deepmerge'; import { deserialize } from './base/deserialize.js'; import { LOGIN_MODE, SMART_ACCOUNT_WALLET_SCOPE, WEB3AUTH_STATE_STORAGE_KEY } from './base/constants.js'; import { WalletInitializationError, WalletLoginError } from './base/errors/index.js'; import { log } from './base/loglevel.js'; import { CONNECTOR_STATUS, CONNECTOR_INITIAL_AUTHENTICATION_MODE, CONNECTOR_EVENTS } from './base/connector/constants.js'; import { Analytics, ANALYTICS_INTEGRATION_TYPE, ANALYTICS_SDK_TYPE, ANALYTICS_EVENTS } from './base/analytics.js'; import { sdkVersion, fetchProjectConfig, withAbort, getErrorAnalyticsProperties, getCaipChainId, isHexStrict, getHostname, getWhitelabelAnalyticsProperties, getAaAnalyticsProperties, getWalletServicesAnalyticsProperties, isBrowser, parseChainNamespaceFromCitadelResponse, normalizeWalletName } from './base/utils.js'; import { WALLET_CONNECTORS } from './base/wallet/index.js'; import { CONNECTOR_NAMESPACES } from './base/chain/IChainInterface.js'; import { CONNECTED_STATUSES, CAN_LOGOUT_STATUSES, CAN_AUTHORIZE_STATUSES } from './base/connector/connectorStatus.js'; import { assertAuthConnector, authConnector, isAuthConnector } from './connectors/auth-connector/authConnector.js'; import { AccountLinkingError } from './account-linking/errors.js'; import { CommonJRPCProvider } from './providers/base-provider/CommonJRPCProvider.js'; import { walletServicesPlugin } from './plugins/wallet-services-plugin/plugin.js'; import { metaMaskConnector, METAMASK_ERC_6963_PROVIDER_RDNS } from './connectors/metamask-connector/metamaskConnector.js'; import { storageAvailable } from './base/connector/utils.js'; import { PLUGIN_STATUS, PLUGIN_NAMESPACES } from './base/plugin/IPlugin.js'; const _excluded = ["walletScope", "eipStandard"]; const PRIMARY_CONNECTED_WALLET_KEY = "__primary__"; class Web3AuthNoModal extends SafeEventEmitter { constructor(options, initialState) { super(); _defineProperty(this, "coreOptions", void 0); _defineProperty(this, "status", CONNECTOR_STATUS.NOT_READY); _defineProperty(this, "loginMode", LOGIN_MODE.NO_MODAL); _defineProperty(this, "aaProvider", null); _defineProperty(this, "connectors", []); _defineProperty(this, "commonJRPCProvider", null); _defineProperty(this, "analytics", void 0); _defineProperty(this, "plugins", {}); _defineProperty(this, "consentRequired", false); _defineProperty(this, "projectConfig", null); _defineProperty(this, "storage", void 0); _defineProperty(this, "connectionReconnected", false); /** Connected wallet state keyed by linked account id; the primary session uses a reserved key. */ _defineProperty(this, "connectedWalletConnectorMap", new Map()); _defineProperty(this, "activeWalletConnectorKey", PRIMARY_CONNECTED_WALLET_KEY); _defineProperty(this, "state", { primaryConnectorName: null, cachedConnector: null, currentChainId: null, idToken: null, accessToken: null, refreshToken: null, activeAccount: null, cachedConnectorNamespace: null }); if (!options.clientId) throw WalletInitializationError.invalidParams("Please provide a valid clientId in constructor"); if (options.enableLogging) log.enableAll();else log.setLevel("error"); if (!options.initialAuthenticationMode) options.initialAuthenticationMode = CONNECTOR_INITIAL_AUTHENTICATION_MODE.CONNECT_AND_SIGN; this.coreOptions = _objectSpread(_objectSpread({}, options), {}, { authBuildEnv: options.authBuildEnv || BUILD_ENV.PRODUCTION }); this.storage = this.getStorageMethod(); this.analytics = new Analytics(); if (options.disableAnalytics) { this.analytics.disable(); } this.analytics.setGlobalProperties({ integration_type: ANALYTICS_INTEGRATION_TYPE.NATIVE_SDK }); this.loadState(initialState).then(() => { if (this.state.idToken && this.coreOptions.ssr && !this.consentRequired) { this.status = this.coreOptions.initialAuthenticationMode === CONNECTOR_INITIAL_AUTHENTICATION_MODE.CONNECT_AND_SIGN ? CONNECTOR_STATUS.AUTHORIZED : CONNECTOR_STATUS.CONNECTED; } return undefined; }).catch(() => {}); } get currentChain() { var _this$coreOptions$cha; if (!this.currentChainId) return undefined; return (_this$coreOptions$cha = this.coreOptions.chains) === null || _this$coreOptions$cha === void 0 ? void 0 : _this$coreOptions$cha.find(chain => chain.chainId === this.currentChainId); } get connected() { return Boolean(this.primaryConnector); } get connection() { return this.getConnectedWalletConnectionByKey(this.activeWalletConnectorKey); } get primaryConnectorName() { return this.state.primaryConnectorName; } get cachedConnector() { return this.state.cachedConnector; } get currentChainId() { var _this$coreOptions$cha2; return this.state.currentChainId || this.coreOptions.defaultChainId || ((_this$coreOptions$cha2 = this.coreOptions.chains) === null || _this$coreOptions$cha2 === void 0 || (_this$coreOptions$cha2 = _this$coreOptions$cha2[0]) === null || _this$coreOptions$cha2 === void 0 ? void 0 : _this$coreOptions$cha2.chainId) || null; } /** * This is always the primary connector that is connected to the user. */ get primaryConnector() { var _this$currentChain; return this.getConnector(this.primaryConnectorName, (_this$currentChain = this.currentChain) === null || _this$currentChain === void 0 ? void 0 : _this$currentChain.chainNamespace); } get accountAbstractionProvider() { return this.aaProvider; } get idToken() { return this.state.idToken || null; } get accessToken() { return this.state.accessToken || null; } get refreshToken() { return this.state.refreshToken || null; } get activeAccount() { return this.state.activeAccount; } /** * This is the current active connector. */ get activeConnector() { const activeConnectedWallet = this.getConnectedWalletConnectorStateByKey(this.activeWalletConnectorKey); if (activeConnectedWallet) { return activeConnectedWallet.connector; } if (this.activeWalletConnectorKey !== PRIMARY_CONNECTED_WALLET_KEY) { throw new Error(`Signing connector not found for account "${this.activeWalletConnectorKey}".`); } return this.primaryConnector; } set provider(_) { throw new Error("Not implemented"); } async init(options) { // init analytics const startTime = Date.now(); this.analytics.init(); this.analytics.identify(this.coreOptions.clientId, { web3auth_client_id: this.coreOptions.clientId, web3auth_network: this.coreOptions.web3AuthNetwork }); this.analytics.setGlobalProperties({ dapp_url: window.location.origin, sdk_name: ANALYTICS_SDK_TYPE.WEB_NO_MODAL, sdk_version: sdkVersion, // Required for organization analytics web3auth_client_id: this.coreOptions.clientId, web3auth_network: this.coreOptions.web3AuthNetwork }); let trackData = {}; try { var _authConnector$authIn, _this$coreOptions$uiC; const { signal } = options || {}; // get project config let projectConfig; try { var _this$coreOptions$acc; projectConfig = await fetchProjectConfig({ clientId: this.coreOptions.clientId, web3AuthNetwork: this.coreOptions.web3AuthNetwork, aaProvider: (_this$coreOptions$acc = this.coreOptions.accountAbstractionConfig) === null || _this$coreOptions$acc === void 0 ? void 0 : _this$coreOptions$acc.smartAccountType, authBuildEnv: this.coreOptions.authBuildEnv }); } catch (e) { const error = await serializeError(e); log.error("Failed to fetch project configurations", error); throw WalletInitializationError.notReady("failed to fetch project configurations", error); } // init config this.projectConfig = projectConfig; this.initAccountAbstractionConfig(projectConfig); this.initChainsConfig(projectConfig); await this.initCachedConnectorAndChainId(); this.initUIConfig(projectConfig); this.initWalletServicesConfig(projectConfig); this.initSessionTimeConfig(projectConfig); this.analytics.setGlobalProperties({ team_id: projectConfig.teamId }); trackData = this.getInitializationTrackData(); // setup common JRPC provider await withAbort(() => this.setupCommonJRPCProvider(), signal); // initialize connectors this.on(CONNECTOR_EVENTS.CONNECTORS_UPDATED, async ({ connectors: newConnectors }) => { const onAbortHandler = () => { var _this$connectors; if (((_this$connectors = this.connectors) === null || _this$connectors === void 0 ? void 0 : _this$connectors.length) > 0) { this.cleanup(); } }; await withAbort(() => Promise.all(newConnectors.map(this.setupConnector.bind(this))), signal, onAbortHandler); // emit connector ready event if (this.status === CONNECTOR_STATUS.NOT_READY) { this.status = CONNECTOR_STATUS.READY; this.emit(CONNECTOR_EVENTS.READY); } }); await withAbort(() => this.loadConnectors({ projectConfig }), signal); await withAbort(() => this.initPlugins(), signal); // track completion event const authConnector = this.getConnector(WALLET_CONNECTORS.AUTH); trackData = _objectSpread(_objectSpread({}, trackData), {}, { connectors: this.connectors.map(connector => connector.name), plugins: Object.keys(this.plugins), auth_ux_mode: (authConnector === null || authConnector === void 0 || (_authConnector$authIn = authConnector.authInstance) === null || _authConnector$authIn === void 0 || (_authConnector$authIn = _authConnector$authIn.options) === null || _authConnector$authIn === void 0 ? void 0 : _authConnector$authIn.uxMode) || ((_this$coreOptions$uiC = this.coreOptions.uiConfig) === null || _this$coreOptions$uiC === void 0 ? void 0 : _this$coreOptions$uiC.uxMode) }); this.analytics.track(ANALYTICS_EVENTS.SDK_INITIALIZATION_COMPLETED, _objectSpread(_objectSpread({}, trackData), {}, { duration: Date.now() - startTime })); } catch (error) { if (error instanceof DOMException && error.name === "AbortError") return; // track failure event this.analytics.track(ANALYTICS_EVENTS.SDK_INITIALIZATION_FAILED, _objectSpread(_objectSpread(_objectSpread({}, trackData), getErrorAnalyticsProperties(error)), {}, { duration: Date.now() - startTime })); log.error("Failed to initialize modal", error); throw error; } } // we need to take into account the chainNamespace as for external connectors, same connector name can be used for multiple chain namespaces getConnector(connectorName, chainNamespace) { return this.connectors.find(connector => { if (connector.name !== connectorName) return false; if (chainNamespace) { if (connector.connectorNamespace === CONNECTOR_NAMESPACES.MULTICHAIN) return true; return connector.connectorNamespace === chainNamespace; } return true; }) || null; } async clearCache() { this.connectedWalletConnectorMap.clear(); this.activeWalletConnectorKey = PRIMARY_CONNECTED_WALLET_KEY; this.connectionReconnected = false; await this.setState({ primaryConnectorName: null, cachedConnector: null, cachedConnectorNamespace: null, currentChainId: null, idToken: null, accessToken: null, refreshToken: null, activeAccount: null, hasUserConsent: undefined }); } async cleanup() { for (const connector of this.connectors) { // if the connector is not ready, we don't need to cleanup // this means that we load the connector (coz of the dashboard config) but the clients did not use it (i.e. with `showOnModal` set to false) // example use case: external wallet **ONLY** login mode but the ClientID has enabled Auth connection in dashboard. if (connector.cleanup && connector.status !== CONNECTOR_STATUS.NOT_READY) await connector.cleanup(); } } async switchChain(params) { var _this$currentChain2; if (params.chainId === ((_this$currentChain2 = this.currentChain) === null || _this$currentChain2 === void 0 ? void 0 : _this$currentChain2.chainId)) return; const newChainConfig = this.coreOptions.chains.find(x => x.chainId === params.chainId); if (!newChainConfig) throw WalletInitializationError.invalidParams("Invalid chainId"); if (CONNECTED_STATUSES.includes(this.status)) { const activeConnector = this.activeConnector; if (!activeConnector) throw WalletInitializationError.notReady("Active signing connector is not ready"); // Single-namespace connectors cannot cross namespace boundaries — MULTICHAIN connectors // (Auth, WC) enforce their own switchChain policy internally. if (activeConnector.connectorNamespace !== CONNECTOR_NAMESPACES.MULTICHAIN && activeConnector.connectorNamespace !== newChainConfig.chainNamespace) { throw WalletLoginError.connectionError(`Cannot switch between chain namespaces with ${activeConnector.name}. Disconnect and reconnect with the target chain.`); } await activeConnector.switchChain(params); return; } if (this.commonJRPCProvider) { await this.commonJRPCProvider.switchChain(params); return; } throw WalletInitializationError.notReady(`No wallet is ready`); } /** * Connect to a specific wallet connector * @param connectorName - Key of the wallet connector to use. */ async connectTo(connectorName, loginParams, loginMode) { this.loginMode = loginMode || "no-modal"; const connector = this.getConnector(connectorName, loginParams === null || loginParams === void 0 ? void 0 : loginParams.chainNamespace); if (!connector || !this.commonJRPCProvider) throw WalletInitializationError.notFound(`Please add wallet connector for ${connectorName} wallet, before connecting`); const initialChain = this.getInitialChainIdForConnector(connector); const finalLoginParams = _objectSpread(_objectSpread({}, loginParams), {}, { chainId: initialChain.chainId, getAuthTokenInfo: this.coreOptions.initialAuthenticationMode === CONNECTOR_INITIAL_AUTHENTICATION_MODE.CONNECT_AND_SIGN }); // track connection started event const startTime = Date.now(); let eventData; if (connectorName === WALLET_CONNECTORS.AUTH) { var _authInstance; const authLoginParams = loginParams; const authConnectionConfig = connector.getOAuthProviderConfig({ authConnection: authLoginParams.authConnection, authConnectionId: authLoginParams.authConnectionId, groupedAuthConnectionId: authLoginParams.groupedAuthConnectionId }); eventData = { connector: connectorName, connector_type: connector.type, chain_id: getCaipChainId(initialChain), chain_name: initialChain.displayName, chain_namespace: initialChain.chainNamespace, auth_connection: authLoginParams.authConnection, auth_connection_id: authLoginParams.authConnectionId, group_auth_connection_id: authLoginParams.groupedAuthConnectionId, mfa_level: authLoginParams.mfaLevel, wallet_key_enabled: authLoginParams.getWalletKey, extra_login_options_enabled: Boolean(authLoginParams.extraLoginOptions), dapp_share_enabled: Boolean(authLoginParams.dappShare), curve: authLoginParams.curve, auth_dapp_url: authLoginParams.dappUrl, is_sfa: Boolean(authLoginParams.idToken), is_default_auth_connection: authConnectionConfig === null || authConnectionConfig === void 0 ? void 0 : authConnectionConfig.isDefault, auth_ux_mode: (_authInstance = connector.authInstance) === null || _authInstance === void 0 || (_authInstance = _authInstance.options) === null || _authInstance === void 0 ? void 0 : _authInstance.uxMode }; } else { eventData = { connector: connectorName, connector_type: connector.type, is_injected: connector.isInjected, chain_id: getCaipChainId(initialChain), chain_name: initialChain.displayName, chain_namespace: initialChain.chainNamespace }; } // track connection started event this.analytics.track(ANALYTICS_EVENTS.CONNECTION_STARTED, eventData); return new Promise((resolve, reject) => { let connectedEventCompleted = false; let authorizedEventReceived = false; const cleanup = () => { this.removeListener(CONNECTOR_EVENTS.CONNECTED, onConnected); this.removeListener(CONNECTOR_EVENTS.ERRORED, onErrored); this.removeListener(CONNECTOR_EVENTS.AUTHORIZED, onAuthorized); }; const checkCompletion = async () => { // In CONNECT_AND_SIGN mode, wait for both connected event and authorized event if (finalLoginParams.getAuthTokenInfo) { if (connectedEventCompleted && authorizedEventReceived) { await completeConnection(); } } else if (connectedEventCompleted) { await completeConnection(); } }; const completeConnection = async () => { try { // track connection completed event const userInfo = await this.getUserInfo(); this.analytics.track(ANALYTICS_EVENTS.CONNECTION_COMPLETED, _objectSpread(_objectSpread({}, eventData), {}, { is_mfa_enabled: userInfo === null || userInfo === void 0 ? void 0 : userInfo.isMfaEnabled, duration: Date.now() - startTime })); cleanup(); resolve(this.connection); } catch (error) { cleanup(); reject(error); } }; const onConnected = async () => { connectedEventCompleted = true; await checkCompletion(); }; const onAuthorized = async () => { authorizedEventReceived = true; await checkCompletion(); }; const onErrored = async err => { // track connection failed event this.analytics.track(ANALYTICS_EVENTS.CONNECTION_FAILED, _objectSpread(_objectSpread(_objectSpread({}, eventData), getErrorAnalyticsProperties(err)), {}, { duration: Date.now() - startTime })); cleanup(); reject(err); }; this.once(CONNECTOR_EVENTS.CONNECTED, onConnected); if (finalLoginParams.getAuthTokenInfo) { this.once(CONNECTOR_EVENTS.AUTHORIZED, onAuthorized); } this.once(CONNECTOR_EVENTS.ERRORED, onErrored); connector.connect(finalLoginParams); this.setCurrentChain(initialChain.chainId); }); } async logout(options = { cleanup: false }) { if (!CAN_LOGOUT_STATUSES.includes(this.status) || !this.primaryConnector) throw WalletLoginError.notConnectedError(`No wallet is connected`); if (this.primaryConnector.status === CONNECTOR_STATUS.DISCONNECTING) return; await this.primaryConnector.disconnect(options); } async getUserInfo() { var _this$primaryConnecto, _userInfo$linkedAccou, _userInfo$linkedAccou2; log.debug("Getting user info", this.status, (_this$primaryConnecto = this.primaryConnector) === null || _this$primaryConnecto === void 0 ? void 0 : _this$primaryConnecto.name); if (!CAN_AUTHORIZE_STATUSES.includes(this.status) || !this.primaryConnector) throw WalletLoginError.notConnectedError(`No wallet is connected`); const userInfo = await this.primaryConnector.getUserInfo(); const linkedAccounts = (_userInfo$linkedAccou = (_userInfo$linkedAccou2 = userInfo.linkedAccounts) === null || _userInfo$linkedAccou2 === void 0 ? void 0 : _userInfo$linkedAccou2.map(account => _objectSpread(_objectSpread({}, account), {}, { active: this.state.activeAccount ? account.id === this.state.activeAccount.id : account.isPrimary }))) !== null && _userInfo$linkedAccou !== void 0 ? _userInfo$linkedAccou : []; this.syncConnectedWalletLinkedAccounts(linkedAccounts); return _objectSpread(_objectSpread({}, userInfo), {}, { linkedAccounts }); } async getLinkedAccounts() { if (!CAN_AUTHORIZE_STATUSES.includes(this.status) || !this.primaryConnector) throw WalletLoginError.notConnectedError(`No wallet is connected`); assertAuthConnector(this.primaryConnector, "Linked accounts can only be fetched when connected with the AUTH connector."); const linkedAccounts = await this.primaryConnector.getLinkedAccounts(); const resolvedLinkedAccounts = linkedAccounts.map(account => _objectSpread(_objectSpread({}, account), {}, { active: this.state.activeAccount ? account.id === this.state.activeAccount.id : account.isPrimary })); this.syncConnectedWalletLinkedAccounts(resolvedLinkedAccounts); return resolvedLinkedAccounts; } getConnectedAccountsWithProviders() { if (!CONNECTED_STATUSES.includes(this.status) || !this.primaryConnector) throw WalletLoginError.notConnectedError(`No wallet is connected`); if (this.status !== CONNECTOR_STATUS.AUTHORIZED) { // before the wallet is authorized, we don't have the user info, so we return an empty array return []; } const connectedAccounts = []; for (const [, value] of this.connectedWalletConnectorMap.entries()) { const hasWalletProvider = Boolean(value.signingProvider || value.solanaWallet); if (hasWalletProvider && this.hasUsableConnectedSwitchConnector(value.connector)) { connectedAccounts.push(value); } } return connectedAccounts; } async enableMFA(loginParams) { var _authConnector$authIn2; if (!CONNECTED_STATUSES.includes(this.status) || !this.primaryConnector) throw WalletLoginError.notConnectedError(`No wallet is connected`); if (this.primaryConnector.name !== WALLET_CONNECTORS.AUTH) throw WalletLoginError.unsupportedOperation(`EnableMFA is not supported for this connector.`); const authConnector = this.primaryConnector; const trackData = { connector: this.primaryConnector.name, auth_ux_mode: (_authConnector$authIn2 = authConnector.authInstance) === null || _authConnector$authIn2 === void 0 || (_authConnector$authIn2 = _authConnector$authIn2.options) === null || _authConnector$authIn2 === void 0 ? void 0 : _authConnector$authIn2.uxMode }; try { this.analytics.track(ANALYTICS_EVENTS.MFA_ENABLEMENT_STARTED, trackData); await this.primaryConnector.enableMFA(loginParams); } catch (error) { this.analytics.track(ANALYTICS_EVENTS.MFA_ENABLEMENT_FAILED, _objectSpread(_objectSpread({}, trackData), getErrorAnalyticsProperties(error))); throw error; } } async manageMFA(loginParams) { var _authConnector$authIn3; if (!CONNECTED_STATUSES.includes(this.status) || !this.primaryConnector) throw WalletLoginError.notConnectedError(`No wallet is connected`); if (this.primaryConnector.name !== WALLET_CONNECTORS.AUTH) throw WalletLoginError.unsupportedOperation(`ManageMFA is not supported for this connector.`); const authConnector = this.primaryConnector; const trackData = { connector: this.primaryConnector.name, auth_ux_mode: (_authConnector$authIn3 = authConnector.authInstance) === null || _authConnector$authIn3 === void 0 || (_authConnector$authIn3 = _authConnector$authIn3.options) === null || _authConnector$authIn3 === void 0 ? void 0 : _authConnector$authIn3.uxMode }; try { this.analytics.track(ANALYTICS_EVENTS.MFA_MANAGEMENT_SELECTED, trackData); await this.primaryConnector.manageMFA(loginParams); } catch (error) { this.analytics.track(ANALYTICS_EVENTS.MFA_MANAGEMENT_FAILED, _objectSpread(_objectSpread({}, trackData), getErrorAnalyticsProperties(error))); throw error; } } async getAuthTokenInfo() { if (!CAN_AUTHORIZE_STATUSES.includes(this.status) || !this.primaryConnector) throw WalletLoginError.notConnectedError(`No wallet is connected`); const trackData = { connector: this.primaryConnector.name }; try { this.analytics.track(ANALYTICS_EVENTS.IDENTITY_TOKEN_STARTED, trackData); // Thread the controller's active chain into connector auth so multichain // connectors sign for the same chain the app/session is currently using. const authTokenInfo = await this.primaryConnector.getAuthTokenInfo(this.currentChainId); this.analytics.track(ANALYTICS_EVENTS.IDENTITY_TOKEN_COMPLETED, trackData); return { idToken: authTokenInfo.idToken }; } catch (error) { this.analytics.track(ANALYTICS_EVENTS.IDENTITY_TOKEN_FAILED, _objectSpread(_objectSpread({}, trackData), getErrorAnalyticsProperties(error))); throw error; } } getPlugin(name) { return this.plugins[name] || null; } async switchAccount(account) { const authConnector = this.getMainAuthConnector(); const switchResult = await authConnector.switchAccount(account, { activeAccount: this.state.activeAccount, currentChainId: this.currentChainId }); if (!switchResult) { return; } try { var _this$projectConfig; await this.processSwitchAccountResult(authConnector, switchResult, { projectConfig: (_this$projectConfig = this.projectConfig) !== null && _this$projectConfig !== void 0 ? _this$projectConfig : undefined }); await authConnector.trackSwitchAccountCompleted(switchResult.targetAccount); } catch (error) { await authConnector.trackSwitchAccountFailed(switchResult.targetAccount, error); throw error; } } async linkAccount(params) { if (!(params !== null && params !== void 0 && params.connectorName)) { throw WalletInitializationError.invalidParams("connectorName is required when calling linkAccount on the no-modal SDK"); } const { chainId } = this.resolveLinkAccountChainConfig(params.chainId); const isolatedConnector = await this.createLinkingWalletConnector(params.connectorName, chainId); return this.linkAccountWithConnector(params.connectorName, chainId, isolatedConnector); } async unlinkAccount(address) { var _await$authConnector$, _this$state$activeAcc; const authConnector = this.getMainAuthConnector(); const linkedAccounts = (_await$authConnector$ = (await authConnector.getUserInfo()).linkedAccounts) !== null && _await$authConnector$ !== void 0 ? _await$authConnector$ : []; const targetAccount = this.findLinkedAccountByAddress(linkedAccounts, address); if (!targetAccount) { throw AccountLinkingError.accountNotLinked(`Account with address "${address}" is not linked`); } if (targetAccount.connector === WALLET_CONNECTORS.AUTH || targetAccount.isPrimary) { throw AccountLinkingError.cannotUnlinkPrimaryAccount(); } if (((_this$state$activeAcc = this.state.activeAccount) === null || _this$state$activeAcc === void 0 ? void 0 : _this$state$activeAcc.id) === targetAccount.id) { throw AccountLinkingError.cannotUnlinkActiveAccount(); } const result = await authConnector.unlinkAccount({ address, authSessionTokens: { accessToken: this.accessToken, idToken: this.idToken } }); await this.setState({ idToken: result.idToken }); // disconnect the connector for unlinked account const connectorToDisconnect = this.getConnectedWalletConnector(targetAccount); if (connectorToDisconnect) { try { if (connectorToDisconnect.connected) { await connectorToDisconnect.disconnect({ cleanup: true }); } } catch (error) { log.debug(`Failed to disconnect linked account "${targetAccount.id}" during unlink`, error); } finally { this.deleteConnectedWalletConnector(targetAccount); } } return result; } setAnalyticsProperties(properties) { this.analytics.setGlobalProperties(properties); } initChainsConfig(projectConfig) { var _this$coreOptions$acc2; // merge chains from project config with core options, core options chains will take precedence over project config chains const chainMap = new Map(); const allChains = [...(projectConfig.chains || []), ...(this.coreOptions.chains || [])]; for (const chain of allChains) { const existingChain = chainMap.get(chain.chainId); if (!existingChain) chainMap.set(chain.chainId, chain);else chainMap.set(chain.chainId, _objectSpread(_objectSpread({}, existingChain), chain)); } this.coreOptions.chains = Array.from(chainMap.values()); // validate chains and namespaces if (this.coreOptions.chains.length === 0) { log.error("chain info not found. Please configure chains on dashboard at https://dashboard.web3auth.io"); throw WalletInitializationError.invalidParams("Please configure chains on dashboard at https://dashboard.web3auth.io"); } const validChainNamespaces = new Set(Object.values(CHAIN_NAMESPACES)); for (const chain of this.coreOptions.chains) { if (!chain.chainNamespace || !validChainNamespaces.has(chain.chainNamespace)) { log.error(`Please provide a valid chainNamespace in chains for chain ${chain.chainId}`); throw WalletInitializationError.invalidParams(`Please provide a valid chainNamespace in chains for chain ${chain.chainId}`); } if (chain.chainNamespace !== CHAIN_NAMESPACES.OTHER && !isHexStrict(chain.chainId)) { log.error(`Please provide a valid chainId in chains for chain ${chain.chainId}`); throw WalletInitializationError.invalidParams(`Please provide a valid chainId as hex string in chains for chain ${chain.chainId}`); } if (chain.chainNamespace !== CHAIN_NAMESPACES.OTHER) { try { new URL(chain.rpcTarget); } catch (error) { // TODO: add support for chain.wsTarget log.error(`Please provide a valid rpcTarget in chains for chain ${chain.chainId}`, error); throw WalletInitializationError.invalidParams(`Please provide a valid rpcTarget in chains for chain ${chain.chainId}`); } } } // if AA is enabled and smart account is not 7702, filter out chains that are not AA-supported const is7702SmartAccount = ((_this$coreOptions$acc2 = this.coreOptions.accountAbstractionConfig) === null || _this$coreOptions$acc2 === void 0 ? void 0 : _this$coreOptions$acc2.smartAccountEipStandard) === SMART_ACCOUNT_EIP_STANDARD.EIP_7702; if (this.coreOptions.accountAbstractionConfig && !is7702SmartAccount) { // write a for loop over accountAbstractionConfig.chains and check if the chainId is valid if (this.coreOptions.accountAbstractionConfig.chains.length === 0) { log.error("Please configure chains for smart accounts on dashboard at https://dashboard.web3auth.io"); throw WalletInitializationError.invalidParams("Please configure chains for smart accounts on dashboard at https://dashboard.web3auth.io"); } for (const chain of this.coreOptions.accountAbstractionConfig.chains) { if (!isHexStrict(chain.chainId)) { log.error(`Please provide a valid chainId in accountAbstractionConfig.chains for chain ${chain.chainId}`); throw WalletInitializationError.invalidParams(`Please provide a valid chainId in accountAbstractionConfig.chains for chain ${chain.chainId}`); } try { var _chain$bundlerConfig; new URL((_chain$bundlerConfig = chain.bundlerConfig) === null || _chain$bundlerConfig === void 0 ? void 0 : _chain$bundlerConfig.url); } catch (error) { log.error(`Please provide a valid bundlerConfig.url in accountAbstractionConfig.chains for chain ${chain.chainId}`, error); throw WalletInitializationError.invalidParams(`Please provide a valid bundlerConfig.url in accountAbstractionConfig.chains for chain ${chain.chainId}`); } if (!chainMap.has(chain.chainId)) { log.error(`Please provide chain config for AA chain in accountAbstractionConfig.chains for chain ${chain.chainId}`); throw WalletInitializationError.invalidParams(`Please provide chain config for AA chain in accountAbstractionConfig.chains for chain ${chain.chainId}`); } } // const aaSupportedChainIds = new Set( // this.coreOptions.accountAbstractionConfig?.chains // ?.filter((chain) => chain.chainId && chain.bundlerConfig?.url) // .map((chain) => chain.chainId) || [] // ); // this.coreOptions.chains = this.coreOptions.chains.filter( // (chain) => chain.chainNamespace !== CHAIN_NAMESPACES.EIP155 || aaSupportedChainIds.has(chain.chainId) // ); // if (this.coreOptions.chains.length === 0) { // log.error("Account Abstraction is enabled but no supported chains found"); // throw WalletInitializationError.invalidParams("Account Abstraction is enabled but no supported chains found"); // } } } initAccountAbstractionConfig(projectConfig) { var _this$coreOptions$acc3; const isAAEnabled = Boolean(this.coreOptions.accountAbstractionConfig || (projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.smartAccounts)); if (!isAAEnabled) return; // merge smart account config from project config with core options, core options will take precedence over project config const _ref = (projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.smartAccounts) || {}, { walletScope, eipStandard } = _ref, configWithoutWalletScope = _objectWithoutProperties(_ref, _excluded); const aaChainMap = new Map(); const allAaChains = [...((configWithoutWalletScope === null || configWithoutWalletScope === void 0 ? void 0 : configWithoutWalletScope.chains) || []), ...(((_this$coreOptions$acc3 = this.coreOptions.accountAbstractionConfig) === null || _this$coreOptions$acc3 === void 0 ? void 0 : _this$coreOptions$acc3.chains) || [])]; for (const chain of allAaChains) { const existingChain = aaChainMap.get(chain.chainId); if (!existingChain) aaChainMap.set(chain.chainId, chain);else aaChainMap.set(chain.chainId, _objectSpread(_objectSpread({}, existingChain), chain)); } this.coreOptions.accountAbstractionConfig = _objectSpread(_objectSpread({ smartAccountEipStandard: eipStandard }, deepmerge(configWithoutWalletScope || {}, this.coreOptions.accountAbstractionConfig || {})), {}, { chains: Array.from(aaChainMap.values()) }); // if eipStandard is 7702, validate smart account type const { smartAccountEipStandard, smartAccountType } = this.coreOptions.accountAbstractionConfig; const is7702SmartAccount = smartAccountEipStandard === SMART_ACCOUNT_EIP_STANDARD.EIP_7702; if (is7702SmartAccount && smartAccountType && !EIP7702_SUPPORTED_SMART_ACCOUNT_TYPES.includes(smartAccountType)) { throw WalletInitializationError.invalidParams(`Smart account type "${smartAccountType}" does not support EIP-7702. Supported: ${EIP7702_SUPPORTED_SMART_ACCOUNT_TYPES.join(", ")}`); } // determine if we should use AA with external wallet if (this.coreOptions.useAAWithExternalWallet === undefined) { this.coreOptions.useAAWithExternalWallet = walletScope === SMART_ACCOUNT_WALLET_SCOPE.ALL; } } initUIConfig(projectConfig) { this.coreOptions.uiConfig = deepmerge.all([{ mode: "light", uxMode: UX_MODE.POPUP }, cloneDeep(projectConfig.whitelabel || {}), this.coreOptions.uiConfig || {}]); } initSessionTimeConfig(projectConfig) { if (this.coreOptions.sessionTime) return; if (projectConfig.sessionTime) this.coreOptions.sessionTime = projectConfig.sessionTime; } async initCachedConnectorAndChainId() { // init chainId using cached chainId if it exists and is valid, otherwise use the defaultChainId or the first chain const cachedChainId = this.state.currentChainId; const isCachedChainIdValid = cachedChainId && this.coreOptions.chains.some(chain => chain.chainId === cachedChainId); if (this.coreOptions.defaultChainId && !isHexStrict(this.coreOptions.defaultChainId)) throw WalletInitializationError.invalidParams("Please provide a valid defaultChainId in constructor"); const currentChainId = isCachedChainIdValid ? cachedChainId : this.coreOptions.defaultChainId || this.coreOptions.chains[0].chainId; await this.setState({ currentChainId }); } initWalletServicesConfig(projectConfig) { var _this$coreOptions$wal, _this$coreOptions$wal2, _this$coreOptions$wal3, _ref2, _this$coreOptions$wal4, _this$coreOptions$wal5; const { enableKeyExport, walletUi } = projectConfig; const { enablePortfolioWidget = false, enableTokenDisplay = true, enableNftDisplay = true, enableWalletConnect = true, enableBuyButton = true, enableSendButton = true, enableSwapButton = true, enableReceiveButton = true, enableShowAllTokensButton = true, enableConfirmationModal = false, enableDefiPositionsDisplay = true, portfolioWidgetPosition = BUTTON_POSITION.BOTTOM_LEFT, defaultPortfolio = "token" } = walletUi || {}; const projectConfigWhiteLabel = { showWidgetButton: enablePortfolioWidget, hideNftDisplay: !enableNftDisplay, hideTokenDisplay: !enableTokenDisplay, hideTransfers: !enableSendButton, hideTopup: !enableBuyButton, hideReceive: !enableReceiveButton, hideSwap: !enableSwapButton, hideShowAllTokens: !enableShowAllTokensButton, hideWalletConnect: !enableWalletConnect, hideDefiPositionsDisplay: !enableDefiPositionsDisplay, buttonPosition: portfolioWidgetPosition, defaultPortfolio }; const whiteLabel = deepmerge.all([projectConfigWhiteLabel, ((_this$coreOptions$wal = this.coreOptions.walletServicesConfig) === null || _this$coreOptions$wal === void 0 ? void 0 : _this$coreOptions$wal.whiteLabel) || {}]); const confirmationStrategy = (_this$coreOptions$wal2 = (_this$coreOptions$wal3 = this.coreOptions.walletServicesConfig) === null || _this$coreOptions$wal3 === void 0 ? void 0 : _this$coreOptions$wal3.confirmationStrategy) !== null && _this$coreOptions$wal2 !== void 0 ? _this$coreOptions$wal2 : enableConfirmationModal ? CONFIRMATION_STRATEGY.MODAL : CONFIRMATION_STRATEGY.AUTO_APPROVE; const isKeyExportEnabled = (_ref2 = (_this$coreOptions$wal4 = (_this$coreOptions$wal5 = this.coreOptions.walletServicesConfig) === null || _this$coreOptions$wal5 === void 0 ? void 0 : _this$coreOptions$wal5.enableKeyExport) !== null && _this$coreOptions$wal4 !== void 0 ? _this$coreOptions$wal4 : enableKeyExport) !== null && _ref2 !== void 0 ? _ref2 : true; this.coreOptions.walletServicesConfig = _objectSpread(_objectSpread({}, this.coreOptions.walletServicesConfig), {}, { confirmationStrategy, whiteLabel, enableKeyExport: isKeyExportEnabled }); } getInitializationTrackData() { try { var _this$coreOptions$cha3, _this$coreOptions$cha4, _this$coreOptions$cha5, _this$coreOptions$cha6, _this$coreOptions$uiC2; const defaultChain = (_this$coreOptions$cha3 = this.coreOptions.chains) === null || _this$coreOptions$cha3 === void 0 ? void 0 : _this$coreOptions$cha3.find(chain => chain.chainId === this.coreOptions.defaultChainId); const rpcHostnames = Array.from(new Set((_this$coreOptions$cha4 = this.coreOptions.chains) === null || _this$coreOptions$cha4 === void 0 ? void 0 : _this$coreOptions$cha4.map(chain => getHostname(chain.rpcTarget)))).filter(Boolean); return _objectSpread(_objectSpread(_objectSpread({ chain_ids: (_this$coreOptions$cha5 = this.coreOptions.chains) === null || _this$coreOptions$cha5 === void 0 ? void 0 : _this$coreOptions$cha5.map(chain => getCaipChainId(chain)), chain_names: (_this$coreOptions$cha6 = this.coreOptions.chains) === null || _this$coreOptions$cha6 === void 0 ? void 0 : _this$coreOptions$cha6.map(chain => chain.displayName), chain_rpc_targets: rpcHostnames, default_chain_id: defaultChain ? getCaipChainId(defaultChain) : undefined, default_chain_name: defaultChain === null || defaultChain === void 0 ? void 0 : defaultChain.displayName, logging_enabled: this.coreOptions.enableLogging, custom_storage: Boolean(this.coreOptions.storage), session_time: this.coreOptions.sessionTime, sfa_key_enabled: this.coreOptions.useSFAKey, mipd_enabled: this.coreOptions.multiInjectedProviderDiscovery, private_key_provider_enabled: Boolean(this.coreOptions.privateKeyProvider), ssr_enabled: this.coreOptions.ssr, auth_build_env: this.coreOptions.authBuildEnv, auth_ux_mode: (_this$coreOptions$uiC2 = this.coreOptions.uiConfig) === null || _this$coreOptions$uiC2 === void 0 ? void 0 : _this$coreOptions$uiC2.uxMode, auth_mfa_level: this.coreOptions.mfaLevel, auth_mfa_settings: Object.keys(this.coreOptions.mfaSettings || {}), aa_enabled_for_external_wallets: this.coreOptions.accountAbstractionConfig ? this.coreOptions.useAAWithExternalWallet : undefined }, getWhitelabelAnalyticsProperties(this.coreOptions.uiConfig)), getAaAnalyticsProperties(this.coreOptions.accountAbstractionConfig)), getWalletServicesAnalyticsProperties(this.coreOptions.walletServicesConfig)); } catch (error) { log.error("Failed to get initialization track data", error); return {}; } } async setupCommonJRPCProvider() { this.commonJRPCProvider = await CommonJRPCProvider.getProviderInstance({ chain: this.currentChain, chains: this.coreOptions.chains }); // sync chainId this.commonJRPCProvider.on("chainChanged", async chainId => { await this.setCurrentChain(chainId); }); } async setupConnector(connector) { this.subscribeToConnectorEvents(connector); try { const initialChain = this.getInitialChainIdForConnector(connector); const autoConnect = this.checkIfAutoConnect(connector); await connector.init({ autoConnect, chainId: initialChain.chainId, getAuthTokenInfo: this.coreOptions.initialAuthenticationMode === CONNECTOR_INITIAL_AUTHENTICATION_MODE.CONNECT_AND_SIGN }); } catch (e) { log.error(e, connector.name); } } async loadConnectors({ projectConfig, modalMode }) { var _this$coreOptions$mul; // always add auth connector const connectorFns = [...(this.coreOptions.connectors || []), authConnector()]; const config = { projectConfig, coreOptions: this.coreOptions, analytics: this.analytics }; // add injected connectors const isExternalWalletEnabled = Boolean(projectConfig.externalWalletAuth); const isMipdEnabled = isExternalWalletEnabled && ((_this$coreOptions$mul = this.coreOptions.multiInjectedProviderDiscovery) !== null && _this$coreOptions$mul !== void 0 ? _this$coreOptions$mul : true); const chainNamespaces = new Set(this.coreOptions.chains.map(chain => chain.chainNamespace)); // prioritize using MM connector over injected connector for EVM chains if (isBrowser() && chainNamespaces.has(CHAIN_NAMESPACES.EIP155)) { // only set headless to true if modal SDK is used, otherwise just use the modal from native Metamask SDK connectorFns.push(metaMaskConnector(modalMode ? { ui: { headless: true } } : undefined)); } if (isMipdEnabled && isBrowser()) { // Solana chains if (chainNamespaces.has(CHAIN_NAMESPACES.SOLANA)) { const { createSolanaMipd, hasSolanaWalletStandardFeatures, walletStandardConnector } = await import('./connectors/injected-solana-connector/index.js'); const solanaMipd = createSolanaMipd(); // subscribe to new injected connectors solanaMipd.on("register", async (...wallets) => { const newConnectors = wallets.filter(hasSolanaWalletStandardFeatures).map(wallet => walletStandardConnector(wallet)(config)); this.setConnectors(newConnectors); }); connectorFns.push(...solanaMipd.get().filter(wallet => hasSolanaWalletStandardFeatures(wallet)).map(walletStandardConnector)); } // EVM chains if (chainNamespaces.has(CHAIN_NAMESPACES.EIP155)) { const { createMipd, injectedEvmConnector } = await import('./connectors/injected-evm-connector/index.js'); const evmMipd = createMipd(); // `@metamask/connect-evm` SDK announces its own EIP-6963 provider (`io.metamask.mmc`) so // it can be discovered by generic wallet pickers. We already register MetaMask via // `metaMaskConnector`, so we must exclude the SDK-announced provider here; otherwise // it is misclassified as an injected wallet and the modal shows MetaMask as installed, // even when the user does not have the extension installed. const isNonSdkAnnouncedProvider = providerDetail => providerDetail.info.rdns !== METAMASK_ERC_6963_PROVIDER_RDNS; // subscribe to new injected connectors evmMipd.subscribe(providerDetails => { const filteredProviderDetails = providerDetails.filter(isNonSdkAnnouncedProvider); const newConnectors = filteredProviderDetails.map(providerDetail => injectedEvmConnector(providerDetail)(config)); this.setConnectors(newConnectors); }); connectorFns.push(...evmMipd.getProviders().filter(isNonSdkAnnouncedProvider).map(injectedEvmConnector)); } } // add WalletConnectV2 connector if external wallets are enabled if (isBrowser() && isExternalWalletEnabled && (chainNamespaces.has(CHAIN_NAMESPACES.SOLANA) || chainNamespaces.has(CHAIN_NAMESPACES.EIP155))) { const { walletConnectV2Connector } = await import('./connectors/wallet-connect-v2-connector/index.js'); connectorFns.push(walletConnectV2Connector()); } const connectors = connectorFns.map(connectorFn => connectorFn(config)); this.setConnectors(connectors); } async initPlugins() { const { chains, plugins } = this.coreOptions; const pluginFns = plugins || []; const isWsSupportedChain = chains.some(x => x.chainNamespace === CHAIN_NAMESPACES.EIP155 || x.chainNamespace === CHAIN_NAMESPACES.SOLANA); if (isWsSupportedChain) { pluginFns.push(walletServicesPlugin()); } for (const pluginFn of pluginFns) { const plugin = pluginFn(); if (!this.plugins[plugin.name]) this.plugins[plugin.name] = plugin; } } setConnectors(connectors) { const getConnectorKey = connector => `${connector.connectorNamespace}-${connector.name}`; const connectorSet = new Set(this.connectors.map(getConnectorKey)); const newConnectors = connectors.map(connector => { const key = getConnectorKey(connector); if (connectorSet.has(key)) return null; connectorSet.add(key); return connector; }).filter(connector => connector !== null); if (newConnectors.length > 0) { this.connectors = [...this.connectors, ...newConnectors]; // only emit new connectors this.emit(CONNECTOR_EVENTS.CONNECTORS_UPDATED, { connectors: newConnectors }); } } subscribeToConnectorEvents(connector) { connector.on(CONNECTOR_EVENTS.CONNECTED, async data => { if (this.primaryConnectorName && this.primaryConnectorName !== data.connectorName) { // Ignore registered connectors that are not the active primary session connector. return; } if (!this.commonJRPCProvider) throw WalletInitializationError.notFound(`CommonJrpcProvider not found`); const { ethereumProvider, solanaWallet } = data; // Seed the primary connector synchronously so AUTHORIZED can resolve a connection // even while we are still restoring a previously active linked wallet. this.setConnectedWalletConnectorState(this.buildImmediateConnectedWalletConnectorState({ connector, ethereumProvider, solanaWallet, usePrimaryProxy: true })); this.setActiveWalletConnectorKey(); this.connectionReconnected = data.reconnected; const { activeAccount, currentChainId } = this.state; // when ssr is enabled, we need to get the idToken from the connector. if (this.coreOptions.ssr) { try { var _data$accessToken, _data$refreshToken; const data = await connector.getAuthTokenInfo(currentChainId); if (!data.idToken) throw WalletLoginError.connectionError("No idToken found"); await this.setState({ idToken: data.idToken, accessToken: (_data$accessToken = data.accessToken) !== null && _data$accessToken !== void 0 ? _data$accessToken : null, refreshToken: (_data$refreshToken = data.refreshToken) !== null && _data$refreshToken !== void 0 ? _data$refreshToken : null }); } catch (er