@web3auth/no-modal
Version:
Multi chain wallet aggregator for web3Auth
965 lines (940 loc) • 97.8 kB
JavaScript
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