@web3auth/no-modal
Version:
Multi chain wallet aggregator for web3Auth
1,826 lines • 101 kB
JavaScript
'use strict';
var _objectWithoutProperties = require('@babel/runtime/helpers/objectWithoutProperties');
var _objectSpread = require('@babel/runtime/helpers/objectSpread2');
var _defineProperty = require('@babel/runtime/helpers/defineProperty');
var baseControllers = require('@toruslabs/base-controllers');
var ethereumControllers = require('@toruslabs/ethereum-controllers');
var auth = require('@web3auth/auth');
var deepmerge = require('deepmerge');
var analytics = require('./base/analytics.js');
var IChainInterface = require('./base/chain/IChainInterface.js');
require('@toruslabs/session-manager');
var index = require('./base/errors/index.js');
var loglevel = require('./base/loglevel.js');
var utils = require('./base/utils.js');
var index$1 = require('./base/wallet/index.js');
var connectorStatus = require('./base/connector/connectorStatus.js');
var constants = require('./base/connector/constants.js');
var utils$1 = require('./base/connector/utils.js');
var constants$1 = require('./base/constants.js');
var deserialize = require('./base/deserialize.js');
require('./base/plugin/errors.js');
var IPlugin = require('./base/plugin/IPlugin.js');
var authConnector = require('./connectors/auth-connector/authConnector.js');
var metamaskConnector = require('./connectors/metamask-connector/metamaskConnector.js');
var plugin = require('./plugins/wallet-services-plugin/plugin.js');
require('./providers/base-provider/utils.js');
var CommonJRPCProvider = require('./providers/base-provider/CommonJRPCProvider.js');
require('./providers/base-provider/commonPrivateKeyProvider.js');
var errors = require('./account-linking/errors.js');
const _excluded = ["walletScope", "eipStandard"];
const PRIMARY_CONNECTED_WALLET_KEY = "__primary__";
class Web3AuthNoModal extends auth.SafeEventEmitter {
constructor(options, initialState) {
super();
_defineProperty(this, "coreOptions", void 0);
_defineProperty(this, "status", constants.CONNECTOR_STATUS.NOT_READY);
_defineProperty(this, "loginMode", constants$1.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 index.WalletInitializationError.invalidParams("Please provide a valid clientId in constructor");
if (options.enableLogging) loglevel.log.enableAll();else loglevel.log.setLevel("error");
if (!options.initialAuthenticationMode) options.initialAuthenticationMode = constants.CONNECTOR_INITIAL_AUTHENTICATION_MODE.CONNECT_AND_SIGN;
this.coreOptions = _objectSpread(_objectSpread({}, options), {}, {
authBuildEnv: options.authBuildEnv || auth.BUILD_ENV.PRODUCTION
});
this.storage = this.getStorageMethod();
this.analytics = new analytics.Analytics();
if (options.disableAnalytics) {
this.analytics.disable();
}
this.analytics.setGlobalProperties({
integration_type: analytics.ANALYTICS_INTEGRATION_TYPE.NATIVE_SDK
});
this.loadState(initialState).then(() => {
if (this.state.idToken && this.coreOptions.ssr && !this.consentRequired) {
this.status = this.coreOptions.initialAuthenticationMode === constants.CONNECTOR_INITIAL_AUTHENTICATION_MODE.CONNECT_AND_SIGN ? constants.CONNECTOR_STATUS.AUTHORIZED : constants.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.ANALYTICS_SDK_TYPE.WEB_NO_MODAL,
sdk_version: utils.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 utils.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 auth.serializeError(e);
loglevel.log.error("Failed to fetch project configurations", error);
throw index.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 utils.withAbort(() => this.setupCommonJRPCProvider(), signal);
// initialize connectors
this.on(constants.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 utils.withAbort(() => Promise.all(newConnectors.map(this.setupConnector.bind(this))), signal, onAbortHandler);
// emit connector ready event
if (this.status === constants.CONNECTOR_STATUS.NOT_READY) {
this.status = constants.CONNECTOR_STATUS.READY;
this.emit(constants.CONNECTOR_EVENTS.READY);
}
});
await utils.withAbort(() => this.loadConnectors({
projectConfig
}), signal);
await utils.withAbort(() => this.initPlugins(), signal);
// track completion event
const authConnector = this.getConnector(index$1.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.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.ANALYTICS_EVENTS.SDK_INITIALIZATION_FAILED, _objectSpread(_objectSpread(_objectSpread({}, trackData), utils.getErrorAnalyticsProperties(error)), {}, {
duration: Date.now() - startTime
}));
loglevel.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 === IChainInterface.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 !== constants.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 index.WalletInitializationError.invalidParams("Invalid chainId");
if (connectorStatus.CONNECTED_STATUSES.includes(this.status)) {
const activeConnector = this.activeConnector;
if (!activeConnector) throw index.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 !== IChainInterface.CONNECTOR_NAMESPACES.MULTICHAIN && activeConnector.connectorNamespace !== newChainConfig.chainNamespace) {
throw index.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 index.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 index.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 === constants.CONNECTOR_INITIAL_AUTHENTICATION_MODE.CONNECT_AND_SIGN
});
// track connection started event
const startTime = Date.now();
let eventData;
if (connectorName === index$1.WALLET_CONNECTORS.AUTH) {
var _connector$authInstan;
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: utils.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: (_connector$authInstan = connector.authInstance) === null || _connector$authInstan === void 0 || (_connector$authInstan = _connector$authInstan.options) === null || _connector$authInstan === void 0 ? void 0 : _connector$authInstan.uxMode
};
} else {
eventData = {
connector: connectorName,
connector_type: connector.type,
is_injected: connector.isInjected,
chain_id: utils.getCaipChainId(initialChain),
chain_name: initialChain.displayName,
chain_namespace: initialChain.chainNamespace
};
}
// track connection started event
this.analytics.track(analytics.ANALYTICS_EVENTS.CONNECTION_STARTED, eventData);
return new Promise((resolve, reject) => {
let connectedEventCompleted = false;
let authorizedEventReceived = false;
const cleanup = () => {
this.removeListener(constants.CONNECTOR_EVENTS.CONNECTED, onConnected);
this.removeListener(constants.CONNECTOR_EVENTS.ERRORED, onErrored);
this.removeListener(constants.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.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.ANALYTICS_EVENTS.CONNECTION_FAILED, _objectSpread(_objectSpread(_objectSpread({}, eventData), utils.getErrorAnalyticsProperties(err)), {}, {
duration: Date.now() - startTime
}));
cleanup();
reject(err);
};
this.once(constants.CONNECTOR_EVENTS.CONNECTED, onConnected);
if (finalLoginParams.getAuthTokenInfo) {
this.once(constants.CONNECTOR_EVENTS.AUTHORIZED, onAuthorized);
}
this.once(constants.CONNECTOR_EVENTS.ERRORED, onErrored);
connector.connect(finalLoginParams);
this.setCurrentChain(initialChain.chainId);
});
}
async logout(options = {
cleanup: false
}) {
if (!connectorStatus.CAN_LOGOUT_STATUSES.includes(this.status) || !this.primaryConnector) throw index.WalletLoginError.notConnectedError(`No wallet is connected`);
if (this.primaryConnector.status === constants.CONNECTOR_STATUS.DISCONNECTING) return;
await this.primaryConnector.disconnect(options);
}
async getUserInfo() {
var _this$primaryConnecto, _userInfo$linkedAccou, _userInfo$linkedAccou2;
loglevel.log.debug("Getting user info", this.status, (_this$primaryConnecto = this.primaryConnector) === null || _this$primaryConnecto === void 0 ? void 0 : _this$primaryConnecto.name);
if (!connectorStatus.CAN_AUTHORIZE_STATUSES.includes(this.status) || !this.primaryConnector) throw index.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 (!connectorStatus.CAN_AUTHORIZE_STATUSES.includes(this.status) || !this.primaryConnector) throw index.WalletLoginError.notConnectedError(`No wallet is connected`);
authConnector.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 (!connectorStatus.CONNECTED_STATUSES.includes(this.status) || !this.primaryConnector) throw index.WalletLoginError.notConnectedError(`No wallet is connected`);
if (this.status !== constants.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 (!connectorStatus.CONNECTED_STATUSES.includes(this.status) || !this.primaryConnector) throw index.WalletLoginError.notConnectedError(`No wallet is connected`);
if (this.primaryConnector.name !== index$1.WALLET_CONNECTORS.AUTH) throw index.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.ANALYTICS_EVENTS.MFA_ENABLEMENT_STARTED, trackData);
await this.primaryConnector.enableMFA(loginParams);
} catch (error) {
this.analytics.track(analytics.ANALYTICS_EVENTS.MFA_ENABLEMENT_FAILED, _objectSpread(_objectSpread({}, trackData), utils.getErrorAnalyticsProperties(error)));
throw error;
}
}
async manageMFA(loginParams) {
var _authConnector$authIn3;
if (!connectorStatus.CONNECTED_STATUSES.includes(this.status) || !this.primaryConnector) throw index.WalletLoginError.notConnectedError(`No wallet is connected`);
if (this.primaryConnector.name !== index$1.WALLET_CONNECTORS.AUTH) throw index.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.ANALYTICS_EVENTS.MFA_MANAGEMENT_SELECTED, trackData);
await this.primaryConnector.manageMFA(loginParams);
} catch (error) {
this.analytics.track(analytics.ANALYTICS_EVENTS.MFA_MANAGEMENT_FAILED, _objectSpread(_objectSpread({}, trackData), utils.getErrorAnalyticsProperties(error)));
throw error;
}
}
async getAuthTokenInfo() {
if (!connectorStatus.CAN_AUTHORIZE_STATUSES.includes(this.status) || !this.primaryConnector) throw index.WalletLoginError.notConnectedError(`No wallet is connected`);
const trackData = {
connector: this.primaryConnector.name
};
try {
this.analytics.track(analytics.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.ANALYTICS_EVENTS.IDENTITY_TOKEN_COMPLETED, trackData);
return {
idToken: authTokenInfo.idToken
};
} catch (error) {
this.analytics.track(analytics.ANALYTICS_EVENTS.IDENTITY_TOKEN_FAILED, _objectSpread(_objectSpread({}, trackData), utils.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 index.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 errors.AccountLinkingError.accountNotLinked(`Account with address "${address}" is not linked`);
}
if (targetAccount.connector === index$1.WALLET_CONNECTORS.AUTH || targetAccount.isPrimary) {
throw errors.AccountLinkingError.cannotUnlinkPrimaryAccount();
}
if (((_this$state$activeAcc = this.state.activeAccount) === null || _this$state$activeAcc === void 0 ? void 0 : _this$state$activeAcc.id) === targetAccount.id) {
throw errors.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) {
loglevel.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) {
loglevel.log.error("chain info not found. Please configure chains on dashboard at https://dashboard.web3auth.io");
throw index.WalletInitializationError.invalidParams("Please configure chains on dashboard at https://dashboard.web3auth.io");
}
const validChainNamespaces = new Set(Object.values(baseControllers.CHAIN_NAMESPACES));
for (const chain of this.coreOptions.chains) {
if (!chain.chainNamespace || !validChainNamespaces.has(chain.chainNamespace)) {
loglevel.log.error(`Please provide a valid chainNamespace in chains for chain ${chain.chainId}`);
throw index.WalletInitializationError.invalidParams(`Please provide a valid chainNamespace in chains for chain ${chain.chainId}`);
}
if (chain.chainNamespace !== baseControllers.CHAIN_NAMESPACES.OTHER && !utils.isHexStrict(chain.chainId)) {
loglevel.log.error(`Please provide a valid chainId in chains for chain ${chain.chainId}`);
throw index.WalletInitializationError.invalidParams(`Please provide a valid chainId as hex string in chains for chain ${chain.chainId}`);
}
if (chain.chainNamespace !== baseControllers.CHAIN_NAMESPACES.OTHER) {
try {
new URL(chain.rpcTarget);
} catch (error) {
// TODO: add support for chain.wsTarget
loglevel.log.error(`Please provide a valid rpcTarget in chains for chain ${chain.chainId}`, error);
throw index.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) === ethereumControllers.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) {
loglevel.log.error("Please configure chains for smart accounts on dashboard at https://dashboard.web3auth.io");
throw index.WalletInitializationError.invalidParams("Please configure chains for smart accounts on dashboard at https://dashboard.web3auth.io");
}
for (const chain of this.coreOptions.accountAbstractionConfig.chains) {
if (!utils.isHexStrict(chain.chainId)) {
loglevel.log.error(`Please provide a valid chainId in accountAbstractionConfig.chains for chain ${chain.chainId}`);
throw index.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) {
loglevel.log.error(`Please provide a valid bundlerConfig.url in accountAbstractionConfig.chains for chain ${chain.chainId}`, error);
throw index.WalletInitializationError.invalidParams(`Please provide a valid bundlerConfig.url in accountAbstractionConfig.chains for chain ${chain.chainId}`);
}
if (!chainMap.has(chain.chainId)) {
loglevel.log.error(`Please provide chain config for AA chain in accountAbstractionConfig.chains for chain ${chain.chainId}`);
throw index.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 === ethereumControllers.SMART_ACCOUNT_EIP_STANDARD.EIP_7702;
if (is7702SmartAccount && smartAccountType && !ethereumControllers.EIP7702_SUPPORTED_SMART_ACCOUNT_TYPES.includes(smartAccountType)) {
throw index.WalletInitializationError.invalidParams(`Smart account type "${smartAccountType}" does not support EIP-7702. Supported: ${ethereumControllers.EIP7702_SUPPORTED_SMART_ACCOUNT_TYPES.join(", ")}`);
}
// determine if we should use AA with external wallet
if (this.coreOptions.useAAWithExternalWallet === undefined) {
this.coreOptions.useAAWithExternalWallet = walletScope === constants$1.SMART_ACCOUNT_WALLET_SCOPE.ALL;
}
}
initUIConfig(projectConfig) {
this.coreOptions.uiConfig = deepmerge.all([{
mode: "light",
uxMode: auth.UX_MODE.POPUP
}, auth.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 && !utils.isHexStrict(this.coreOptions.defaultChainId)) throw index.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 = baseControllers.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 ? baseControllers.CONFIRMATION_STRATEGY.MODAL : baseControllers.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 => utils.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 => utils.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 ? utils.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
}, utils.getWhitelabelAnalyticsProperties(this.coreOptions.uiConfig)), utils.getAaAnalyticsProperties(this.coreOptions.accountAbstractionConfig)), utils.getWalletServicesAnalyticsProperties(this.coreOptions.walletServicesConfig));
} catch (error) {
loglevel.log.error("Failed to get initialization track data", error);
return {};
}
}
async setupCommonJRPCProvider() {
this.commonJRPCProvider = await CommonJRPCProvider.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 === constants.CONNECTOR_INITIAL_AUTHENTICATION_MODE.CONNECT_AND_SIGN
});
} catch (e) {
loglevel.log.error(e, connector.name);
}
}
async loadConnectors({
projectConfig,
modalMode
}) {
var _this$coreOptions$mul;
// always add auth connector
const connectorFns = [...(this.coreOptions.connectors || []), authConnector.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 (utils.isBrowser() && chainNamespaces.has(baseControllers.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.metaMaskConnector(modalMode ? {
ui: {
headless: true
}
} : undefined));
}
if (isMipdEnabled && utils.isBrowser()) {
// Solana chains
if (chainNamespaces.has(baseControllers.CHAIN_NAMESPACES.SOLANA)) {
const {
createSolanaMipd,
hasSolanaWalletStandardFeatures,
walletStandardConnector
} = await Promise.resolve().then(function () { return require('./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(baseControllers.CHAIN_NAMESPACES.EIP155)) {
const {
createMipd,
injectedEvmConnector
} = await Promise.resolve().then(function () { return require('./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 !== metamaskConnector.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 (utils.isBrowser() && isExternalWalletEnabled && (chainNamespaces.has(baseControllers.CHAIN_NAMESPACES.SOLANA) || chainNamespaces.has(baseControllers.CHAIN_NAMESPACES.EIP155))) {
const {
walletConnectV2Connector
} = await Promise.resolve().then(function () { return require('./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 === baseControllers.CHAIN_NAMESPACES.EIP155 || x.chainNamespace === baseControllers.CHAIN_NAMESPACES.SOLANA);
if (isWsSupportedChain) {
pluginFns.push(plugin.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(constants.CONNECTOR_EVENTS.CONNECTORS_UPDATED, {
connectors: newConnectors
});
}
}
subscribeToConnectorEvents(connector) {
connector.on(constants.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 index.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 index.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 (error) {
loglevel.log.error(error);
this.deleteConnectedWalletConnector();
this.setActiveWalletConnectorKey();
this.status = constants.CONNECTOR_STATUS.ERRORED;
this.emit(constants.CONNECTOR_EVENTS.ERRORED, error, this.loginMode);
return;
}
}
// The following block only hits during rehydration
let rehydrateWithLinkedAccount = false;
// for rehydration, if the active account is not the primary account, i.e. not `null`, create an isolated connector and connect to the chain
if (activeAccount && !activeAccount.isPrimary && activeAccount.connector !== index$1.WALLET_CONNECTORS.AUTH) {
var _ref3, _walletConnector$prov, _linkedAccountConnect, _ref4, _walletConnector$sola, _linkedAccountConnect2;
const accountLinkingConnector = authConnector.isAuthConnector(connector) ? connector : this.getConnector(index$1.WALLET_CONNECTORS.AUTH);
authConnector.assertAuthConnector(accountLinkingConnector, "Account switching requires the AUTH connector to be available.");
const targetChainId = accountLinkingConnector.getChainIdForLinkedAccount(activeAccount, currentChainId);
const walletConnector = await this.createIsolatedWalletConnector(activeAccount.connector, targetChainId);
let linkedAccountConnection = null;
if (!this.hasUsableConnectedSwitchConnector(walletConnector)) {
linkedAccountConnection = await walletConnector.connect({
chainId: targetChainId
});
if (!linkedAccountConnection) {
throw errors.AccountLinkingError.requestFailed(`Failed to connect isolated connector "${activeAccount.connector}" for account switch.`);
}
}
const connectedWalletState = await this.resolveConnectedWalletConnectorState({
connector: walletConnector,
ethereumProvider: (_ref3 = (_walletConnector$prov = walletConnector.provider) !== null && _walletConnector$prov !== void 0 ? _walletConnector$prov : (_linkedAccountConnect = linkedAccountConnection) === null || _linkedAccountConnect === void 0 ? void 0 : _linkedAccountConnect.ethereumProvider) !== null && _ref3 !== void 0 ? _ref3 : null,
solanaWallet: (_ref4 = (_walletConnector$sola = walletConnector.solanaWallet) !== null && _walletConnector$sola !== void 0 ? _walletConnector$sola : (_linkedAccountConnect2 = linkedAccountConnection) === null || _linkedAccountConnect2 === void 0 ? void 0 : _linkedAccountConnect2.solanaWallet) !== null && _ref4 !== void 0 ? _ref4 : null,
usePrimaryProxy: false,
account: activeAccount
});
this.setConnectedWalletConnectorState(connectedWalletState, activeAccount);
this.setActiveWalletConnectorKey(activeAccount);
rehydrateWithLinkedAccount = true;
}
if (ethereumProvider) {
await this.bindPrimaryEthereumSigningProxy(ethereumProvider, data.connectorName);
}
const primaryConnectedWalletState = await this.resolveConnectedWalletConnectorState({
connector,
ethereumProvider,
solanaWallet,
usePrimaryProxy: true
});
this.setConnectedWalletConnectorState(primaryConnectedWalletState);
await this.setState({
primaryConnectorName: data.connectorName
});
this.cacheWallet(data.connectorName, data.connectorNamespace);
const isConnectAndSign = this.coreOptions.initialAuthenticationMode === constants.CONNECTOR_INITIAL_AUTHENTICATION_MODE.CONNECT_AND_SIGN;
const pendingUserConsent = this.consentRequired && !this.state.hasUserConsent;
if (pendingUserConsent && !isConnectAndSign) {
this.status = constants.CONNECTOR_STATUS.CONSENT_REQUIRING;
this.emit(constants.CONNECTOR_EVENTS.CONSENT_REQUIRING, _objectSpread({}, data));
loglevel.log.debug("consent_requiring", this.status, this.primaryConnectorName);
} else {
// In CONNECT_AND_SIGN mode the AUTHORIZED handler can run before this point (e.g. when `ssr=true`
// this handler `await`s `connector.getAuthTokenInfo()` which fires AUTHORIZED mid-execution).
// Don't downgrade an already-advanced status (CONSENT_REQUIRING or AUTHORIZED) back to CONNECTED;
// otherwise `acceptConsent` would throw "Cannot accept consent: not in consent_requiring state".
if (this.status !== constants.CONNECTOR_STATUS.CONSENT_REQUIRING && this.status !== constants.CONNECTOR_STATUS.AUTHORIZED) {
this.status = constants.CONNECTOR_STATUS.CONNECTED;
}
// Defer plugin connection until consent is accepted; otherwise plugins would start before the consent step completes.
// `completeConsentAcceptance` connects the plugins once the user accepts the consent.
if (!pendingUserConsent) {
this.connectToPlugins(_objectSpread(_objectSpread({}, data), {}, {
connector: data.connectorName
}));
}
// `pendingUserConsent` signals listeners (LoginModal, React/Vue contexts) to skip processing this CONNECTED event,
// so the upcoming AUTHORIZED -> CONSENT_REQUIRING transition is not overridden by a late CONNECTED handler in CONNECT_AND_SIGN mode.
this.emit(constants.CONNECTOR_EVENTS.CONNECTED, _objectSpread(_objectSpread({}, data), {}, {
loginMode: this.loginMode,
pendingUserConsent
}));
// if we're rehydrating with a linked account, we need to emit a CONNECTION_UPDATED event
// so that upstream listeners and context are updated with the linked connection.
if (rehydrateWithLinkedAccount) {
this.emit(constants.CONNECTOR_EVENTS.CONNECTION_UPDATED, this.connection);
}
}
});
connector.on(constants.CONNECTOR_EVENTS.DISCONNECTED, async data => {
if (this.shouldIgnoreInactiveConnectorEvent(connector, constants.CONNECTOR_EVENTS.DISCONNECTED)) return;
const disconnectedConnector = data === null || data === void 0 ? void 0 : data.connector;
const {
activeAccount
} = this.state;
if (!activeAccount || activeAccount && activeAccount.isPrimary || disconnectedConnector === index$1.WALLET_CONNECTORS.AUTH) {
// If the primary session disconnects, tear down every other connected wallet connector
// and clear the entire map.
await Promise.all(Array.from(this.connectedWalletConnectorMap.entries()).map(async ([accountId, connectedWallet]) => {
if (connectedWallet.connector === connector) {
this.connectedWalletConnectorMap.delete(accountId);
return;
}
try {
if (connectedWallet.connected && connectedWallet.connector.connected) {
await connectedWallet.connector.disconnect({
cleanup: true
});
}
} catch (error) {
loglevel.log.debug("Connected wallet connector disconnect on primary disconnect", error);
} finally {
this.connectedWalletConnectorMap.delete(accountId);
}
}));
}
this.connectedWalletConnectorMap.clear();
this.activeWalletConnectorKey = PRIMARY_CONNECTED_WALLET_KEY;
this.connectionReconnected = false;
// re-setup commonJRPCProvider
this.commonJRPCProvider.removeAllListeners();
this.setupCommonJRPCProvider();
// get back to ready state for rehydrating.
this.status = constants.CONNECTOR_STATUS.READY;
const cachedConnector = this.state.cachedConnector;
if (this.primaryConnectorName === cachedConnector) {
await this.clearCache();
}
loglevel.log.debug("disconnected", this.status, this.primaryConnectorName);
await Promise.all(Object.values(this.plugins).map(async plugin => {
if (!plugin.SUPPORTED_CONNECTORS.includes(connector.name)) return;
if (plugin.status !== IPlugin.PLUGIN_STATUS.CONNECTED) return;
return plugin.disconnect().catch(error => {
// swallow error if connector doesn't supports this plugin.
if (error.code === 5211) {
return;
}
// throw error;
loglevel.log.error(error);
});
}));
await this.setState({
primaryConnectorName: null,
hasUserConsent: undefined,
activeAccount: null
});
this.emit(constants.CONNECTOR_EVENTS.DISCONNECTED);
});
connector.on(constants.CONNECTOR_EVENTS.CONNECTING, data => {
this.status = constants.CONNECTOR_STATUS.CONNECTING;
this.emit(constants.CONNECTOR_EVENTS.CONNECTING, data);
loglevel.log.debug("connecting", this.status, this.primaryConnectorName);
});
connector.on(constants.CONNECTOR_EVENTS.ERRORED, async data => {
if (this.shouldIgnoreInactiveConnectorEvent(connector, constants.CONNECTOR_EVENTS.ERRORED)) {
loglevel.log.error("Inactive connector emitted errored event", {
connector: connector.name,
error: data
});
return;
}
this.status = constants.CONNECTOR_STATUS.ERRORED;
await this.clearCache();
this.emit(constants.CONNECTOR_EVENTS.ERRORED, data, this.loginMode);
loglevel.log.debug("errored", this.status, this.primaryConnectorName);
});
connector.on(constants.CONNECTOR_EVENTS.REHYDRATION_ERROR, async error => {
if (this.shouldIgnoreInactiveConnectorEvent(connector, constants.CONNECTOR_EVENTS.REHYDRATION_ERROR)) {
loglevel.log.error("Inactive connector emitted rehydration error", {
connector: connector.name,
error
});
return;
}
this.status = constants.CONNECTOR_STATUS.READY;
await this.clearCache();
this.emit(constants.CONNECTOR_EVENTS.REHYDRATION_ERROR, error);
});
connector.on(constants.CONNECTOR_EVENTS.CONNECTOR_DATA_UPDATED, async data => {
if (this.shouldIgnoreInactiveConnectorEvent(connector, constants.CONNECTOR_EVENTS.CONNECTOR_DATA_UPDATED)) return;
// External wallets can resolve to a different active chain than the requested one,
// so let connector-reported chain updates reconcile Web3Auth state after connect.
if (typeof (data === null || data === void 0 ? void 0 : data.data) === "object" && (data === null || data === void 0 ? void 0 : data.data) !== null && "chainId" in data.data && typeof data.data.chainId === "string") {
const previousChain = this.currentChain;
await this.setCurrentChain(data.data.chainId);
const currentChain = this.currentChain;
const connectorEthereumProvider = connector.provider;
if ((previousChain === null || previousChain === void 0 ? void 0 : previousChain.chainNamespace) !== (currentChain === null || currentChain === void 0 ? void 0 : currentChain.chainNamespace) && (currentChain === null || currentChain === void 0 ? void 0 : currentChain.chainNamespace) === baseControllers.CHAIN_NAMESPACES.EIP155 && connectorEthereumProvider) {
// from sol -> evm namespace switch, we need to re-create AccountAbstractionProvider
await this.bindPrimaryEthereumSigningProxy(connectorEthereumProvider, connector.name);
}
}
loglevel.log.debug("connector data updated", data);
this.emit(constants.CONNECTOR_EVENTS.CONNECTOR_DATA_UPDATED, data);
});
connector.on(constants.CONNECTOR_EVENTS.CACHE_CLEAR, async data => {
if (this.shouldIgnoreInactiveConnectorEvent(connector, constants.CONNECTOR_EVENTS.CACHE_CLEAR)) return;
loglevel.log.debug("connector cache clear", data);
await this.clearCache();
});
connector.on(constants.CONNECTOR_EVENTS.MFA_ENABLED, isMFAEnabled => {
var _authConnector$authIn4;
loglevel.log.debug("mfa enabled", isMFAEnabled);
const authConnector = this.primaryConnector;
// mfa_enabled event is only emitted when using "popup" ux_mode
// TODO: handle mfa_enabled event when using "redirect" ux_mode
this.analytics.track(analytics.ANALYTICS_EVENTS.MFA_ENABLEMENT_COMPLETED, {
connector: this.primaryConnector.name,
auth_ux_mode: (_authConnector$authIn4 = authConnector.authInstance) === null || _authConnector$authIn4 === void 0 || (_authConnector$authIn4 = _authConnector$authIn4.options) === null || _authConnector$authIn4 === void 0 ? void 0 : _authConnector$authIn4.uxMode,
is_mfa_enabled: isMFAEnabled
});
this.emit(constants.CONNECTOR_EVENTS.MFA_ENABLED, isMFAEnabled);
});
connector.on(constants.CONNECTOR_EVENTS.AUTHORIZING, data => {
this.status = constants.CONNECTOR_STATUS.AUTHORIZING;
this.emit(constants.CONNECTOR_EVENTS.AUTHORIZING, data);
loglevel.log.debug("authorizing", this.status, this.primaryConnectorName);
});
connector.on(constants.CONNECTOR_EVENTS.AUTHORIZED, async data => {
var _data$authTokenInfo$a, _data$authTokenInfo$r;
await this.setState({
idToken: data.authTokenInfo.idToken,
accessToken: (_data$authTokenInfo$a = data.authTokenInfo.accessToken) !== null && _data$authTokenInfo$a !== void 0 ? _data$authTokenInfo$a : null,
refreshToken: (_data$authTokenInfo$r = data.authTokenInfo.refreshToken) !== null && _data$authTokenInfo$r !== void 0 ? _data$authTokenInfo$r : null
});
// if the user has not consented yet, we will ask for consent
if (this.consentRequired && this.connection && !this.state.hasUserConsent) {
this.status = constants.CONNECTOR_STATUS.CONSENT_REQUIRING;
this.emit(constants.CONNECTOR_EVENTS.CONSENT_REQUIRING, {
connectorName: data.connector
});
loglevel.log.debug("consent_requiring", this.status, this.primaryConnectorName);
} else {
this.status = constants.CONNECTOR_STATUS.AUTHORIZED;
this.emit(constants.CONNECTOR_EVENTS.AUTHORIZED, data);
loglevel.log.debug("authorized", this.status, this.primaryConnectorName);
}
});
}
checkInitRequirements() {
if (this.status === constants.CONNECTOR_STATUS.READY) throw index.WalletInitializationError.notReady("Connector is already initialized");
}
checkIfAutoConnect(connector) {
var _this$currentChain3;
let autoConnect = this.cachedConnector === connector.name && this.state.cachedConnectorNamespace === connector.connectorNamespace;
if (autoConnect && (_this$currentChain3 = this.currentChain) !== null && _this$currentChain3 !== void 0 && _this$currentChain3.chainNamespace) {
if (connector.connectorNamespace === IChainInterface.CONNECTOR_NAMESPACES.MULTICHAIN) autoConnect = true;else autoConnect = connector.connectorNamespace === this.currentChain.chainNamespace;
}
return autoConnect;
}
/**
* Gets the initial chain configuration for a connector
* @throws WalletInitializationError If no chain is found for the connector's namespace
*/
getInitialChainIdForConnector(connector) {
var _initialChain;
let initialChain = this.currentChain;
const defaultChainId = this.coreOptions.defaultChainId;
const isMultiChainConnector = connector.connectorNamespace === IChainInterface.CONNECTOR_NAMESPACES.MULTICHAIN;
// if the connector is a multi-chain connector and a default chain id is set, use the default chain id
if (isMultiChainConnector && defaultChainId) {
initialChain = this.coreOptions.chains.find(chain => chain.chainId === defaultChainId) || this.currentChain;
} else if (((_initialChain = initialChain) === null || _initialChain === void 0 ? void 0 : _initialChain.chainNamespace) !== connector.connectorNamespace && connector.connectorNamespace !== IChainInterface.CONNECTOR_NAMESPACES.MULTICHAIN) {
initialChain = this.coreOptions.chains.find(x => x.chainNamespace === connector.connectorNamespace);
}
if (!initialChain) throw index.WalletInitializationError.invalidParams(`No chain found for ${connector.connectorNamespace}`);
return initialChain;
}
async completeConsentAcceptance() {
const connection = this.connection;
if (!connection) {
throw index.WalletLoginError.connectionError("Cannot accept consent: no active connection");
}
if (this.status !== constants.CONNECTOR_STATUS.CONSENT_REQUIRING) {
throw index.WalletLoginError.connectionError("Cannot accept consent: not in consent_requiring state");
}
await this.setState({
hasUserConsent: true
});
const isConnectAndSign = this.coreOptions.initialAuthenticationMode === constants.CONNECTOR_INITIAL_AUTHENTICATION_MODE.CONNECT_AND_SIGN;
if (isConnectAndSign && this.state.idToken) {
this.status = constants.CONNECTOR_STATUS.AUTHORIZED;
loglevel.log.debug("consent accepted, authorized", this.status, this.primaryConnectorName);
} else {
this.status = constants.CONNECTOR_STATUS.CONNECTED;
loglevel.log.debug("consent accepted, connected", this.status, this.primaryConnectorName);
}
// connect to wallet-service plugin
if (this.primaryConnectorName === index$1.WALLET_CONNECTORS.AUTH) {
this.connectToPlugins({
connector: this.primaryConnectorName
});
}
this.emit(constants.CONNECTOR_EVENTS.CONSENT_ACCEPTED, {
reconnected: this.connectionReconnected
});
}
resolveLinkAccountChainConfig(chainId) {
var _this$coreOptions$cha7;
const finalChainId = chainId || this.state.currentChainId;
const chainConfig = (_this$coreOptions$cha7 = this.coreOptions.chains) === null || _this$coreOptions$cha7 === void 0 ? void 0 : _this$coreOptions$cha7.find(chain => chain.chainId === finalChainId);
if (!chainConfig) {
throw errors.AccountLinkingError.walletProofFailed("No chainId is available. Please specify chainId in LinkAccountParams or ensure the SDK has an active chain.");
}
return chainConfig;
}
/**
* Resolves the chain ID for a switch account operation.
* If the account's chain namespace is the same as the current chain namespace, return the current chain ID.
* If the account's chain namespace is different from the current chain namespace, return the chainId the account was linked in.
*
* @param account - The account to switch to.
* @param activeChainId - The current active chain ID.
* @returns The resolved chain ID.
*/
resolveSwitchAccountChainId(account, activeChainId) {
const targetChainNamespace = account.chainNamespace ? utils.parseChainNamespaceFromCitadelResponse(account.chainNamespace) : null;
if (targetChainNamespace && this.currentChain.chainNamespace === targetChainNamespace) {
return this.currentChain.chainId;
}
return activeChainId;
}
async createLinkingWalletConnector(connectorName, chainId, config) {
try {
const linkingConnector = await this.createIsolatedWalletConnector(connectorName, chainId, config);
return linkingConnector;
} catch (error) {
if (error instanceof errors.AccountLinkingError && error.code === 5405) {
throw error;
}
throw errors.AccountLinkingError.walletProofFailed(error instanceof Error ? error.message : String(error), error);
}
}
async createSwitchingWalletConnector(connectorName, chainId, config) {
return this.createIsolatedWalletConnector(connectorName, chainId, config);
}
getConnectedWalletConnector(account) {
var _this$getConnectedWal, _this$getConnectedWal2;
return (_this$getConnectedWal = (_this$getConnectedWal2 = this.getConnectedWalletConnectorState(account)) === null || _this$getConnectedWal2 === void 0 ? void 0 : _this$getConnectedWal2.connector) !== null && _this$getConnectedWal !== void 0 ? _this$getConnectedWal : null;
}
getConnectedWalletConnectorState(account) {
return this.getConnectedWalletConnectorStateByKey(this.getConnectedWalletConnectorKey(account));
}
setConnectedWalletConnectorState(connectedWallet, account) {
this.connectedWalletConnectorMap.set(this.getConnectedWalletConnectorKey(account), connectedWallet);
}
setConnectedWalletConnector(connector, account) {
var _connector$solanaWall;
this.setConnectedWalletConnectorState(_objectSpread(_objectSpread({}, this.getConnectedWalletLinkedAccountInfo(account)), {}, {
connector,
signingProvider: connector.provider,
solanaWallet: (_connector$solanaWall = connector.solanaWallet) !== null && _connector$solanaWall !== void 0 ? _connector$solanaWall : null,
connected: connector.connected || connector.status === constants.CONNECTOR_STATUS.CONNECTED
}), account);
}
deleteConnectedWalletConnector(account) {
this.connectedWalletConnectorMap.delete(this.getConnectedWalletConnectorKey(account));
}
getConnectedWalletConnection(account) {
return this.getConnectedWalletConnectionByKey(this.getConnectedWalletConnectorKey(account));
}
hasUsableConnectedSwitchConnector(connector) {
if (!connector) return false;
const isConnected = connector.connected || connector.status === constants.CONNECTOR_STATUS.CONNECTED;
return Boolean(isConnected && (connector.provider || connector.solanaWallet));
}
setActiveWalletConnectorKey(account) {
this.activeWalletConnectorKey = this.getConnectedWalletConnectorKey(account);
}
getConnectedWalletConnectorKey(account) {
return !account || account.isPrimary ? PRIMARY_CONNECTED_WALLET_KEY : account.id;
}
getConnectedWalletConnectorStateByKey(accountKey) {
var _this$connectedWallet;
return (_this$connectedWallet = this.connectedWalletConnectorMap.get(accountKey)) !== null && _this$connectedWallet !== void 0 ? _this$connectedWallet : null;
}
isLinkedAccountInfo(account) {
return Boolean(account && "connector" in account);
}
toConnectedWalletLinkedAccountInfo(account) {
return {
id: account.id,
isPrimary: account.isPrimary,
eoaAddress: account.eoaAddress,
aaAddress: account.aaAddress,
aaProvider: account.aaProvider,
active: account.active,
accountType: account.accountType,
address: account.address,
authConnectionId: account.authConnectionId,
groupedAuthConnectionId: account.groupedAuthConnectionId,
chainNamespace: account.chainNamespace
};
}
getConnectedWalletLinkedAccountInfo(account) {
const existingConnectedWallet = this.getConnectedWalletConnectorState(account);
const resolvedAccount = this.isLinkedAccountInfo(account) ? account : existingConnectedWallet;
if (resolvedAccount) {
return this.toConnectedWalletLinkedAccountInfo(resolvedAccount);
}
const isPrimaryAccount = !account || account.isPrimary;
const accountId = account && !account.isPrimary ? account.id : PRIMARY_CONNECTED_WALLET_KEY;
return {
id: accountId,
isPrimary: isPrimaryAccount,
eoaAddress: "",
aaAddress: undefined,
aaProvider: undefined,
active: this.state.activeAccount ? this.state.activeAccount.id === accountId : isPrimaryAccount,
accountType: "",
address: null,
authConnectionId: null,
groupedAuthConnectionId: null,
chainNamespace: null
};
}
syncConnectedWalletLinkedAccounts(linkedAccounts) {
for (const linkedAccount of linkedAccounts) {
const accountKey = this.getConnectedWalletConnectorKey(linkedAccount);
const connectedWallet = this.connectedWalletConnectorMap.get(accountKey);
if (!connectedWallet) {
continue;
}
this.connectedWalletConnectorMap.set(accountKey, _objectSpread(_objectSpread({}, connectedWallet), this.toConnectedWalletLinkedAccountInfo(linkedAccount)));
}
}
refreshConnectedWalletActiveStates(activeAccount) {
for (const [accountKey, connectedWallet] of this.connectedWalletConnectorMap.entries()) {
const isPrimaryAccount = accountKey === PRIMARY_CONNECTED_WALLET_KEY || connectedWallet.isPrimary;
this.connectedWalletConnectorMap.set(accountKey, _objectSpread(_objectSpread({}, connectedWallet), {}, {
active: activeAccount ? connectedWallet.id === activeAccount.id : isPrimaryAccount
}));
}
}
getConnectedWalletConnectionByKey(accountKey) {
const connectedWallet = this.getConnectedWalletConnectorStateByKey(accountKey);
if (!connectedWallet) {
return null;
}
if (!connectedWallet.signingProvider && !connectedWallet.solanaWallet) {
throw new Error(`Connected connector "${connectedWallet.connector.name}" is not ready.`);
}
return this.buildConnectionFromConnectedWalletConnectorState(connectedWallet);
}
buildConnectionFromConnectedWalletConnectorState(connectedWallet) {
var _connectedWallet$sola;
return {
ethereumProvider: connectedWallet.signingProvider,
solanaWallet: (_connectedWallet$sola = connectedWallet.solanaWallet) !== null && _connectedWallet$sola !== void 0 ? _connectedWallet$sola : null,
connectorName: connectedWallet.connector.name,
connectorNamespace: connectedWallet.connector.connectorNamespace
};
}
buildImmediateConnectedWalletConnectorState(params) {
var _this$commonJRPCProvi;
const {
connector,
ethereumProvider,
solanaWallet,
usePrimaryProxy,
account
} = params;
const isSolanaOnly = connector.connectorNamespace === baseControllers.CHAIN_NAMESPACES.SOLANA;
const connectedWallet = _objectSpread(_objectSpread({}, this.getConnectedWalletLinkedAccountInfo(account)), {}, {
connector,
signingProvider: isSolanaOnly ? null : ethereumProvider ? usePrimaryProxy ? (_this$commonJRPCProvi = this.commonJRPCProvider) !== null && _this$commonJRPCProvi !== void 0 ? _this$commonJRPCProvi : ethereumProvider : ethereumProvider : null,
solanaWallet: solanaWallet !== null && solanaWallet !== void 0 ? solanaWallet : null,
connected: connector.connected || connector.status === constants.CONNECTOR_STATUS.CONNECTED || connector.status === constants.CONNECTOR_STATUS.AUTHORIZED
});
return connectedWallet;
}
async resolveConnectedWalletConnectorState(params) {
const {
connector,
ethereumProvider,
solanaWallet,
usePrimaryProxy,
account
} = params;
return this.buildImmediateConnectedWalletConnectorState({
connector,
ethereumProvider,
solanaWallet,
usePrimaryProxy,
account
});
}
async linkAccountWithConnector(connectorName, chainId, connectorToLink) {
const authConnector = this.getMainAuthConnector();
const result = await authConnector.linkAccount({
connectorName,
chainId,
connectorToLink,
authSessionTokens: {
accessToken: this.accessToken,
idToken: this.idToken
}
});
await this.setState({
idToken: result.idToken
});
await this.cacheConnectedLinkedWalletConnector(authConnector, connectorToLink);
return result;
}
getMainAuthConnector() {
if (!connectorStatus.CONNECTED_STATUSES.includes(this.status) || !this.primaryConnector) {
throw index.WalletLoginError.notConnectedError("No wallet is connected. Connect with AUTH before unlinking an account.");
}
const mainConnector = this.primaryConnector;
authConnector.assertAuthConnector(mainConnector, "Account linking is only supported when connected with the AUTH connector.");
return mainConnector;
}
/**
* Processes the result of a switch account operation.
*
* - If the target account is a primary account, we will switch back to the primary account.
* - If the target account is an external account and already connected (i.e. connector is available with connected state), we will just switch to it without re-connecting again.
* - If the target account is an external account and not connected (i.e. connector is not available with connected state), we will create a new isolated connector and connect to it.
* @param authConnector - The main auth connector to use.
* @param switchResult - The result of the switch account operation.
* @param options - The options for the switch account operation.
* @returns A promise that resolves when the switch account operation is complete.
*/
async processSwitchAccountResult(authConnector, switchResult, options = {}) {
const resolvedSwitchChainId = this.resolveSwitchAccountChainId(switchResult.targetAccount, switchResult.activeChainId);
if (switchResult.kind === "primary") {
var _primaryConnectedWall, _this$commonJRPCProvi2, _switchResult$solanaW;
const existingPrimaryConnectedWalletState = this.getConnectedWalletConnectorState();
const primaryConnectedWalletState = existingPrimaryConnectedWalletState !== null && existingPrimaryConnectedWalletState !== void 0 ? existingPrimaryConnectedWalletState : await this.resolveConnectedWalletConnectorState({
connector: authConnector,
ethereumProvider: switchResult.ethereumProvider,
solanaWallet: switchResult.solanaWallet,
usePrimaryProxy: true,
account: switchResult.targetAccount
});
this.setConnectedWalletConnectorState(_objectSpread(_objectSpread({}, primaryConnectedWalletState), {}, {
connector: authConnector,
signingProvider: (_primaryConnectedWall = primaryConnectedWalletState.signingProvider) !== null && _primaryConnectedWall !== void 0 ? _primaryConnectedWall : switchResult.ethereumProvider ? (_this$commonJRPCProvi2 = this.commonJRPCProvider) !== null && _this$commonJRPCProvi2 !== void 0 ? _this$commonJRPCProvi2 : switchResult.ethereumProvider : null,
solanaWallet: (_switchResult$solanaW = switchResult.solanaWallet) !== null && _switchResult$solanaW !== void 0 ? _switchResult$solanaW : primaryConnectedWalletState.solanaWallet,
connected: authConnector.connected || authConnector.status === constants.CONNECTOR_STATUS.CONNECTED
}));
this.setActiveWalletConnectorKey();
} else {
var _ref5, _options$walletConnec;
const walletConnector = (_ref5 = (_options$walletConnec = options.walletConnector) !== null && _options$walletConnec !== void 0 ? _options$walletConnec : this.getConnectedWalletConnector(switchResult.targetAccount)) !== null && _ref5 !== void 0 ? _ref5 : await this.createSwitchingWalletConnector(switchResult.targetAccount.connector, resolvedSwitchChainId, options.projectConfig);
let linkedAccountConnection = null;
try {
var _ref6, _walletConnector$prov2, _linkedAccountConnect3, _ref7, _walletConnector$sola2, _linkedAccountConnect4;
if (!this.hasUsableConnectedSwitchConnector(walletConnector)) {
const switchChainConfig = this.coreOptions.chains.find(c => c.chainId === resolvedSwitchChainId);
if (!switchChainConfig) {
throw index.WalletLoginError.connectionError(`Chain config is not available for chain ${resolvedSwitchChainId}`);
}
const caipChainId = utils.getCaipChainId(switchChainConfig);
const caipAccountId = `${caipChainId}:${switchResult.targetAccount.eoaAddress}`;
linkedAccountConnection = await walletConnector.connect({
chainId: resolvedSwitchChainId,
caipAccountIds: [caipAccountId]
});
if (!linkedAccountConnection) {
throw errors.AccountLinkingError.requestFailed(`Failed to connect isolated connector "${switchResult.targetAccount.connector}" for account switch.`);
}
}
await authConnector.assertSwitchAccountConnectorMatchesTarget(walletConnector, switchResult.targetAccount);
const connectedWalletState = await this.resolveConnectedWalletConnectorState({
connector: walletConnector,
ethereumProvider: (_ref6 = (_walletConnector$prov2 = walletConnector.provider) !== null && _walletConnector$prov2 !== void 0 ? _walletConnector$prov2 : (_linkedAccountConnect3 = linkedAccountConnection) === null || _linkedAccountConnect3 === void 0 ? void 0 : _linkedAccountConnect3.ethereumProvider) !== null && _ref6 !== void 0 ? _ref6 : null,
solanaWallet: (_ref7 = (_walletConnector$sola2 = walletConnector.solanaWallet) !== null && _walletConnector$sola2 !== void 0 ? _walletConnector$sola2 : (_linkedAccountConnect4 = linkedAccountConnection) === null || _linkedAccountConnect4 === void 0 ? void 0 : _linkedAccountConnect4.solanaWallet) !== null && _ref7 !== void 0 ? _ref7 : null,
usePrimaryProxy: false,
account: switchResult.targetAccount
});
this.setConnectedWalletConnectorState(connectedWalletState, switchResult.targetAccount);
this.setActiveWalletConnectorKey(switchResult.targetAccount);
} catch (error) {
throw authConnector.toSwitchAccountConnectorError(switchResult.targetAccount, error);
}
}
await this.setCurrentChain(resolvedSwitchChainId);
await this.setState({
activeAccount: switchResult.activeAccount
});
this.syncConnectedWalletLinkedAccounts([switchResult.targetAccount]);
this.refreshConnectedWalletActiveStates(switchResult.activeAccount);
const connection = this.connection;
if (!connection) {
throw index.WalletLoginError.connectionError("Failed to resolve the active connection after switching accounts.");
}
this.emit(constants.CONNECTOR_EVENTS.CONNECTION_UPDATED, connection);
}
isActiveConnectorEventSource(connector) {
if (!this.primaryConnectorName) return true;
const activeConnector = this.primaryConnector;
if (activeConnector) return activeConnector === connector;
return connector.name === this.primaryConnectorName;
}
shouldIgnoreInactiveConnectorEvent(connector, event) {
if (this.isActiveConnectorEventSource(connector)) return false;
loglevel.log.debug("Ignoring connector lifecycle event from inactive connector", {
event,
sourceConnector: connector.name,
activeConnector: this.primaryConnectorName
});
return true;
}
findLinkedAccountByAddress(linkedAccounts, address) {
var _linkedAccounts$find;
const normalizedAddress = address.toLowerCase();
return (_linkedAccounts$find = linkedAccounts.find(account => {
var _account$address, _account$eoaAddress;
if (!account.chainNamespace || utils.parseChainNamespaceFromCitadelResponse(account.chainNamespace) !== baseControllers.CHAIN_NAMESPACES.EIP155) {
return false;
}
return ((_account$address = account.address) === null || _account$address === void 0 ? void 0 : _account$address.toLowerCase()) === normalizedAddress || ((_account$eoaAddress = account.eoaAddress) === null || _account$eoaAddress === void 0 ? void 0 : _account$eoaAddress.toLowerCase()) === normalizedAddress;
})) !== null && _linkedAccounts$find !== void 0 ? _linkedAccounts$find : null;
}
findLinkedAccountByWalletAddress(linkedAccounts, address) {
var _linkedAccounts$find2;
return (_linkedAccounts$find2 = linkedAccounts.find(account => {
if (!account.chainNamespace) {
return false;
}
const chainNamespace = utils.parseChainNamespaceFromCitadelResponse(account.chainNamespace);
if (chainNamespace === baseControllers.CHAIN_NAMESPACES.EIP155) {
var _account$address2, _account$eoaAddress2;
const normalizedAddress = address.toLowerCase();
return ((_account$address2 = account.address) === null || _account$address2 === void 0 ? void 0 : _account$address2.toLowerCase()) === normalizedAddress || ((_account$eoaAddress2 = account.eoaAddress) === null || _account$eoaAddress2 === void 0 ? void 0 : _account$eoaAddress2.toLowerCase()) === normalizedAddress;
}
if (chainNamespace === baseControllers.CHAIN_NAMESPACES.SOLANA) {
return account.address === address || account.eoaAddress === address;
}
return false;
})) !== null && _linkedAccounts$find2 !== void 0 ? _linkedAccounts$find2 : null;
}
async getConnectedWalletAddress(connector) {
var _connector$solanaWall2, _accounts$;
const solanaAddress = (_connector$solanaWall2 = connector.solanaWallet) === null || _connector$solanaWall2 === void 0 || (_connector$solanaWall2 = _connector$solanaWall2.accounts) === null || _connector$solanaWall2 === void 0 || (_connector$solanaWall2 = _connector$solanaWall2[0]) === null || _connector$solanaWall2 === void 0 ? void 0 : _connector$solanaWall2.address;
if (solanaAddress) {
return solanaAddress;
}
if (!connector.provider) {
return null;
}
const accounts = await connector.provider.request({
method: "eth_accounts"
});
return (_accounts$ = accounts === null || accounts === void 0 ? void 0 : accounts[0]) !== null && _accounts$ !== void 0 ? _accounts$ : null;
}
async cacheConnectedLinkedWalletConnector(authConnector, walletConnector) {
try {
var _await$authConnector$2;
const connectedWalletAddress = await this.getConnectedWalletAddress(walletConnector);
if (!connectedWalletAddress) {
return;
}
const linkedAccounts = (_await$authConnector$2 = await authConnector.getLinkedAccounts()) !== null && _await$authConnector$2 !== void 0 ? _await$authConnector$2 : [];
const linkedAccount = this.findLinkedAccountByWalletAddress(linkedAccounts, connectedWalletAddress);
if (linkedAccount && !linkedAccount.isPrimary) {
const connectedWalletState = await this.resolveConnectedWalletConnectorState({
connector: walletConnector,
ethereumProvider: walletConnector.provider,
solanaWallet: walletConnector.solanaWallet,
usePrimaryProxy: false,
account: linkedAccount
});
this.setConnectedWalletConnectorState(connectedWalletState, linkedAccount);
}
} catch (error) {
loglevel.log.debug("Failed to cache connected linked wallet connector", error);
}
}
async cacheWallet(walletName, connectorNamespace) {
await this.setState({
cachedConnector: walletName,
cachedConnectorNamespace: connectorNamespace
});
}
async setCurrentChain(chainId) {
const {
currentChainId
} = this.state;
if (chainId === currentChainId) return;
const newChain = this.coreOptions.chains.find(chain => chain.chainId === chainId);
if (!newChain) throw index.WalletInitializationError.invalidParams(`Invalid chainId: ${chainId}`);
await this.setState({
currentChainId: chainId
});
}
connectToPlugins(data) {
Object.values(this.plugins).map(async plugin => {
try {
var _this$currentChain4;
// skip if it's not compatible with the connector
if (!plugin.SUPPORTED_CONNECTORS.includes(data.connector)) return;
// skip if it's not compatible with the current chain
if (plugin.pluginNamespace !== IPlugin.PLUGIN_NAMESPACES.MULTICHAIN && plugin.pluginNamespace !== ((_this$currentChain4 = this.currentChain) === null || _this$currentChain4 === void 0 ? void 0 : _this$currentChain4.chainNamespace)) return;
// skip if it's already connected
if (plugin.status === IPlugin.PLUGIN_STATUS.CONNECTED) return;
await plugin.initWithWeb3Auth(this, this.coreOptions.uiConfig, this.analytics);
await plugin.connect();
} catch (error) {
// swallow error if connector connector doesn't supports this plugin.
if (error.code === 5211) {
return;
}
loglevel.log.error(error);
}
});
}
async bindPrimaryEthereumSigningProxy(ethereumProvider, connectorName) {
var _this$primaryConnecto2, _this$currentChain5, _accountAbstractionCo;
if (!this.commonJRPCProvider) throw index.WalletInitializationError.notFound(`CommonJrpcProvider not found`);
const primaryConnectorProvider = connectorName === this.primaryConnectorName ? (_this$primaryConnecto2 = this.primaryConnector) === null || _this$primaryConnecto2 === void 0 ? void 0 : _this$primaryConnecto2.provider : null;
const baseEthereumProvider = ethereumProvider === this.commonJRPCProvider || ethereumProvider === this.aaProvider ? primaryConnectorProvider !== null && primaryConnectorProvider !== void 0 ? primaryConnectorProvider : ethereumProvider : ethereumProvider;
// For WalletConnect v2, bind the provider wrapper: its inner `.provider` is a
// per-chain engine that is replaced on every chain switch and never emits events,
// so binding it leaves commonJRPCProvider on the connect-time chain forever.
let finalProvider = connectorName === index$1.WALLET_CONNECTORS.WALLET_CONNECT_V2 ? baseEthereumProvider : (baseEthereumProvider === null || baseEthereumProvider === void 0 ? void 0 : baseEthereumProvider.provider) || baseEthereumProvider;
const {
accountAbstractionConfig
} = this.coreOptions;
const is7702 = (accountAbstractionConfig === null || accountAbstractionConfig === void 0 ? void 0 : accountAbstractionConfig.smartAccountEipStandard) === ethereumControllers.SMART_ACCOUNT_EIP_STANDARD["EIP_7702"];
const isAaSupportedForCurrentChain = ((_this$currentChain5 = this.currentChain) === null || _this$currentChain5 === void 0 ? void 0 : _this$currentChain5.chainNamespace) === baseControllers.CHAIN_NAMESPACES.EIP155 && (accountAbstractionConfig === null || accountAbstractionConfig === void 0 || (_accountAbstractionCo = accountAbstractionConfig.chains) === null || _accountAbstractionCo === void 0 ? void 0 : _accountAbstractionCo.some(chain => {
var _this$currentChain6;
return chain.chainId === ((_this$currentChain6 = this.currentChain) === null || _this$currentChain6 === void 0 ? void 0 : _this$currentChain6.chainId);
}));
// setup AA provider if AA is enabled (skip for EIP-7702; 7702 uses EOA + 5792/7702 RPC only)
// Skip AA wrapping for Base Account as it's already a smart account provider
if (!is7702 && isAaSupportedForCurrentChain && (connectorName === index$1.WALLET_CONNECTORS.AUTH || this.coreOptions.useAAWithExternalWallet && connectorName !== index$1.WALLET_CONNECTORS.BASE_ACCOUNT)) {
var _accountAbstractionCo2;
const {
accountAbstractionProvider,
toEoaProvider
} = await Promise.resolve().then(function () { return require('./providers/account-abstraction-provider/index.js'); });
const eoaProvider = connectorName === index$1.WALLET_CONNECTORS.AUTH ? await toEoaProvider(baseEthereumProvider) : baseEthereumProvider;
const aaChainIds = new Set((accountAbstractionConfig === null || accountAbstractionConfig === void 0 || (_accountAbstractionCo2 = accountAbstractionConfig.chains) === null || _accountAbstractionCo2 === void 0 ? void 0 : _accountAbstractionCo2.map(chain => chain.chainId)) || []);
const aaProvider = await accountAbstractionProvider({
accountAbstractionConfig,
provider: eoaProvider,
chain: this.currentChain,
chains: this.coreOptions.chains.filter(chain => aaChainIds.has(chain.chainId)),
useProviderAsTransport: connectorName === index$1.WALLET_CONNECTORS.AUTH
});
this.aaProvider = aaProvider;
if (connectorName !== index$1.WALLET_CONNECTORS.AUTH && this.coreOptions.useAAWithExternalWallet) {
finalProvider = this.aaProvider;
}
}
this.commonJRPCProvider.updateProviderEngineProxy(finalProvider);
}
getChainConfigForIsolatedConnector(chainId) {
const chainConfig = this.coreOptions.chains.find(chain => chain.chainId === chainId);
if (!chainConfig) {
throw index.WalletInitializationError.invalidParams(`Chain config is not available for chain ${chainId}`);
}
return chainConfig;
}
async resolveInstalledDiscoveredWalletConnector(params) {
const {
connectorName,
chainConfig,
config,
isMipdEnabled
} = params;
if (!utils.isBrowser() || !isMipdEnabled) return null;
if (chainConfig.chainNamespace === baseControllers.CHAIN_NAMESPACES.EIP155) {
const {
createMipd,
injectedEvmConnector
} = await Promise.resolve().then(function () { return require('./connectors/injected-evm-connector/index.js'); });
const providerDetail = createMipd().getProviders().find(detail => utils.normalizeWalletName(detail.info.name) === connectorName);
if (providerDetail) {
return injectedEvmConnector(providerDetail)(config);
}
return null;
}
if (chainConfig.chainNamespace === baseControllers.CHAIN_NAMESPACES.SOLANA) {
const {
createSolanaMipd,
hasSolanaWalletStandardFeatures,
walletStandardConnector
} = await Promise.resolve().then(function () { return require('./connectors/injected-solana-connector/index.js'); });
const wallet = createSolanaMipd().get().find(candidate => hasSolanaWalletStandardFeatures(candidate) && utils.normalizeWalletName(candidate.name) === connectorName);
if (wallet) {
return walletStandardConnector(wallet)(config);
}
}
return null;
}
async resolveDiscoveredWalletConnector(connectorName, chainId, config, effectiveProjectConfig) {
var _this$coreOptions$mul2;
const chainConfig = this.getChainConfigForIsolatedConnector(chainId);
const isExternalWalletEnabled = Boolean(effectiveProjectConfig === null || effectiveProjectConfig === void 0 ? void 0 : effectiveProjectConfig.externalWalletAuth);
const isMipdEnabled = isExternalWalletEnabled && ((_this$coreOptions$mul2 = this.coreOptions.multiInjectedProviderDiscovery) !== null && _this$coreOptions$mul2 !== void 0 ? _this$coreOptions$mul2 : true);
const installedConnector = await this.resolveInstalledDiscoveredWalletConnector({
connectorName,
chainConfig,
config,
isMipdEnabled
});
if (installedConnector) {
return installedConnector;
}
const isBuiltInConnectorName = Object.values(index$1.WALLET_CONNECTORS).includes(connectorName);
const supportsWalletConnectFallback = chainConfig.chainNamespace === baseControllers.CHAIN_NAMESPACES.EIP155 || chainConfig.chainNamespace === baseControllers.CHAIN_NAMESPACES.SOLANA;
// Named discovered wallets (for example Phantom) can reuse WalletConnect as a transport fallback
// when an injected connector for the target chain namespace is unavailable.
if (!isBuiltInConnectorName && isExternalWalletEnabled && supportsWalletConnectFallback) {
const {
walletConnectV2Connector
} = await Promise.resolve().then(function () { return require('./connectors/wallet-connect-v2-connector/index.js'); });
return walletConnectV2Connector()(config);
}
throw errors.AccountLinkingError.unsupportedConnector(`Connector "${connectorName}" does not support automatic wallet linking. ` + `Use ${index$1.WALLET_CONNECTORS.METAMASK}, ${index$1.WALLET_CONNECTORS.WALLET_CONNECT_V2}, or an installed compatible wallet.`);
}
/**
* Create a new connector instance that is NOT registered in this.connectors and NOT
* subscribed to the main SDK event loop. Its lifecycle events are therefore isolated
* and will not mutate any global SDK state (connectedConnectorName, connection, idToken).
*/
async createIsolatedWalletConnector(connectorName, chainId, projectConfig) {
var _ref8;
const effectiveProjectConfig = (_ref8 = projectConfig !== null && projectConfig !== void 0 ? projectConfig : this.projectConfig) !== null && _ref8 !== void 0 ? _ref8 : undefined;
const config = {
projectConfig: effectiveProjectConfig,
coreOptions: this.coreOptions,
analytics: this.analytics
};
let connector;
switch (connectorName) {
case index$1.WALLET_CONNECTORS.METAMASK:
connector = metamaskConnector.metaMaskConnector()(config);
break;
case index$1.WALLET_CONNECTORS.WALLET_CONNECT_V2:
{
const {
walletConnectV2Connector
} = await Promise.resolve().then(function () { return require('./connectors/wallet-connect-v2-connector/index.js'); });
connector = walletConnectV2Connector()(config);
break;
}
case index$1.WALLET_CONNECTORS.AUTH:
throw errors.AccountLinkingError.unsupportedConnector(`Connector "${connectorName}" does not support automatic wallet linking.`);
default:
{
connector = await this.resolveDiscoveredWalletConnector(connectorName, chainId, config, effectiveProjectConfig);
break;
}
}
// Init the isolated connector WITHOUT subscribing to the main event loop.
// This is the key difference from setupConnector(), which calls subscribeToConnectorEvents().
// autoConnect: false ensures the connector does not attempt to rehydrate a previous session.
await connector.init({
chainId,
autoConnect: false
});
return connector;
}
async setState(newState) {
this.state = _objectSpread(_objectSpread({}, this.state), newState);
await this.storage.set(constants$1.WEB3AUTH_STATE_STORAGE_KEY, JSON.stringify(this.state));
}
async loadState(initialState) {
if (initialState) {
this.state = _objectSpread(_objectSpread({}, this.state), initialState);
return;
}
const state = await this.storage.get(constants$1.WEB3AUTH_STATE_STORAGE_KEY);
if (!state) return;
this.state = deserialize.deserialize(state);
}
getStorageMethod() {
var _this$coreOptions$sto;
if ((_this$coreOptions$sto = this.coreOptions.storage) !== null && _this$coreOptions$sto !== void 0 && _this$coreOptions$sto.sessionId) return this.coreOptions.storage.sessionId;
if (this.coreOptions.ssr) return new auth.CookieStorage({
maxAge: this.coreOptions.sessionTime
});
if (utils$1.storageAvailable("localStorage")) return new auth.LocalStorageAdapter();
return new auth.MemoryStorage();
}
}
exports.Web3AuthNoModal = Web3AuthNoModal;