UNPKG

@web3auth/no-modal

Version:
557 lines (553 loc) 25.9 kB
'use strict'; var _objectSpread = require('@babel/runtime/helpers/objectSpread2'); var _defineProperty = require('@babel/runtime/helpers/defineProperty'); var baseControllers = require('@toruslabs/base-controllers'); var Client = require('@walletconnect/sign-client'); var utils$1 = require('@walletconnect/utils'); var wsEmbed = require('@web3auth/ws-embed'); var deepmerge = require('deepmerge'); var siwe = require('viem/siwe'); var analytics = require('../../base/analytics.js'); var IChainInterface = require('../../base/chain/IChainInterface.js'); var baseConnector = require('../../base/connector/baseConnector.js'); require('../../base/connector/connectorStatus.js'); var constants = require('../../base/connector/constants.js'); require('@web3auth/auth'); require('jwt-decode'); var constants$1 = require('../../base/constants.js'); var index$1 = require('../../base/errors/index.js'); var loglevel = require('../../base/loglevel.js'); require('../../base/plugin/errors.js'); require('../../base/plugin/IPlugin.js'); var utils = require('../../base/utils.js'); var index = require('../../base/wallet/index.js'); var config = require('./config.js'); var WalletConnectV2Provider = require('./WalletConnectV2Provider.js'); var wcSolanaWallet = require('./wcSolanaWallet.js'); var solana = require('../../base/wallet/solana.js'); class WalletConnectV2Connector extends baseConnector.BaseConnector { constructor(options) { var _this$connectorOption, _this$connectorOption2, _this$connectorOption3, _this$connectorOption4, _this$connectorOption5; super(options); _defineProperty(this, "name", index.WALLET_CONNECTORS.WALLET_CONNECT_V2); _defineProperty(this, "connectorNamespace", IChainInterface.CONNECTOR_NAMESPACES.MULTICHAIN); _defineProperty(this, "currentChainNamespace", baseControllers.CHAIN_NAMESPACES.OTHER); _defineProperty(this, "type", constants.CONNECTOR_CATEGORY.EXTERNAL); _defineProperty(this, "connectorOptions", void 0); _defineProperty(this, "status", constants.CONNECTOR_STATUS.NOT_READY); _defineProperty(this, "connectorData", { uri: "" }); _defineProperty(this, "connector", null); _defineProperty(this, "activeSession", null); _defineProperty(this, "wcProvider", null); _defineProperty(this, "_solanaWallet", null); _defineProperty(this, "analytics", void 0); this.connectorOptions = _objectSpread({}, options); const { qrcodeModal, walletConnectInitOptions } = (options === null || options === void 0 ? void 0 : options.connectorSettings) || {}; this.connectorOptions = _objectSpread(_objectSpread({}, this.connectorOptions), {}, { connectorSettings: (_this$connectorOption = (_this$connectorOption2 = this.connectorOptions) === null || _this$connectorOption2 === void 0 ? void 0 : _this$connectorOption2.connectorSettings) !== null && _this$connectorOption !== void 0 ? _this$connectorOption : {}, loginSettings: (_this$connectorOption3 = (_this$connectorOption4 = this.connectorOptions) === null || _this$connectorOption4 === void 0 ? void 0 : _this$connectorOption4.loginSettings) !== null && _this$connectorOption3 !== void 0 ? _this$connectorOption3 : {} }); this.analytics = options.analytics; if (qrcodeModal) this.connectorOptions.connectorSettings.qrcodeModal = qrcodeModal; if (walletConnectInitOptions) this.connectorOptions.connectorSettings.walletConnectInitOptions = _objectSpread(_objectSpread({}, (_this$connectorOption5 = this.connectorOptions.connectorSettings.walletConnectInitOptions) !== null && _this$connectorOption5 !== void 0 ? _this$connectorOption5 : {}), walletConnectInitOptions); const { loginSettings } = options; if (loginSettings) this.connectorOptions.loginSettings = _objectSpread(_objectSpread({}, this.connectorOptions.loginSettings || {}), loginSettings); } get connected() { return !!this.activeSession; } get provider() { if (this.status !== constants.CONNECTOR_STATUS.NOT_READY && this.wcProvider) { return this.wcProvider; } return null; } get solanaWallet() { return this._solanaWallet; } set provider(_) { throw new Error("Not implemented"); } async init(options) { var _this$connectorOption6; const chainConfig = this.coreOptions.chains.find(x => x.chainId === options.chainId); super.checkInitializationRequirements({ chainConfig }); const projectId = (_this$connectorOption6 = this.connectorOptions.connectorSettings) === null || _this$connectorOption6 === void 0 || (_this$connectorOption6 = _this$connectorOption6.walletConnectInitOptions) === null || _this$connectorOption6 === void 0 ? void 0 : _this$connectorOption6.projectId; const filteredChains = this.coreOptions.chains.filter(x => x.chainNamespace === baseControllers.CHAIN_NAMESPACES.EIP155 || x.chainNamespace === baseControllers.CHAIN_NAMESPACES.SOLANA); if (filteredChains.length === 0) { throw index$1.WalletInitializationError.invalidParams("No supported chains found"); } const wc2Settings = await config.getWalletConnectV2Settings(filteredChains, projectId); if (!this.connectorOptions.loginSettings || Object.keys(this.connectorOptions.loginSettings).length === 0) { this.connectorOptions.loginSettings = wc2Settings.loginSettings; } this.connectorOptions.connectorSettings = deepmerge(wc2Settings.connectorSettings || {}, this.connectorOptions.connectorSettings || {}); const { connectorSettings } = this.connectorOptions; this.connector = await Client.init(connectorSettings === null || connectorSettings === void 0 ? void 0 : connectorSettings.walletConnectInitOptions); this.wcProvider = new WalletConnectV2Provider.WalletConnectV2Provider({ config: { chain: chainConfig, chains: this.coreOptions.chains }, connector: this.connector }); this.emit(constants.CONNECTOR_EVENTS.READY, index.WALLET_CONNECTORS.WALLET_CONNECT_V2); this.status = constants.CONNECTOR_STATUS.READY; loglevel.log.debug("initializing wallet connect v2 connector"); if (options.autoConnect) { await this.checkForPersistedSession(); if (this.connected) { this.rehydrated = true; try { await this.onConnectHandler({ chain: chainConfig, getAuthTokenInfo: options.getAuthTokenInfo }); } catch (error) { loglevel.log.error("wallet auto connect", error); this.emit(constants.CONNECTOR_EVENTS.REHYDRATION_ERROR, error); } } else { this.status = constants.CONNECTOR_STATUS.NOT_READY; this.emit(constants.CONNECTOR_EVENTS.CACHE_CLEAR); } } } async connect({ chainId, getAuthTokenInfo }) { super.checkConnectionRequirements(); const chainConfig = this.coreOptions.chains.find(x => x.chainId === chainId); if (!chainConfig) throw index$1.WalletLoginError.connectionError("Chain config is not available"); if (!this.connector) throw index$1.WalletInitializationError.notReady("Wallet connector is not ready yet"); // Skip tracking for rehydration since only new connections are tracked // Track when connection completes since it auto-initializes to generate QR code const shouldTrack = !this.connected && !this.rehydrated; const startTime = Date.now(); let eventData = { connector: this.name, connector_type: this.type, is_injected: this.isInjected, is_wallet_connect: true, chain_id: utils.getCaipChainId(chainConfig), chain_name: chainConfig === null || chainConfig === void 0 ? void 0 : chainConfig.displayName, chain_namespace: chainConfig === null || chainConfig === void 0 ? void 0 : chainConfig.chainNamespace }; try { const trackCompletionEvents = () => { // track connection events if (shouldTrack) { var _this$activeSession, _this$analytics, _this$analytics2; const { name, url, redirect } = ((_this$activeSession = this.activeSession) === null || _this$activeSession === void 0 || (_this$activeSession = _this$activeSession.peer) === null || _this$activeSession === void 0 ? void 0 : _this$activeSession.metadata) || {}; const { native, universal, linkMode } = redirect || {}; eventData = _objectSpread(_objectSpread({}, eventData), {}, { connector: name || this.name, wallet_url: url, redirect_native: native, redirect_universal: universal, redirect_link_mode_enabled: linkMode }); (_this$analytics = this.analytics) === null || _this$analytics === void 0 || _this$analytics.track(analytics.ANALYTICS_EVENTS.CONNECTION_STARTED, eventData); (_this$analytics2 = this.analytics) === null || _this$analytics2 === void 0 || _this$analytics2.track(analytics.ANALYTICS_EVENTS.CONNECTION_COMPLETED, _objectSpread(_objectSpread({}, eventData), {}, { duration: Date.now() - startTime })); } }; const connectorNamespace = this.connectorNamespace; // if already connected if (this.connected) { await this.onConnectHandler({ chain: chainConfig, getAuthTokenInfo }); return { ethereumProvider: this.provider, solanaWallet: this._solanaWallet, connectorName: this.name, connectorNamespace }; } if (this.status !== constants.CONNECTOR_STATUS.CONNECTING) { await this.createNewSession({ chainConfig, trackCompletionEvents, getAuthTokenInfo }); } return { ethereumProvider: this.provider, solanaWallet: this._solanaWallet, connectorName: this.name, connectorNamespace }; } catch (error) { loglevel.log.error("Wallet connect v2 connector error while connecting", error); // ready again to be connected this.status = constants.CONNECTOR_STATUS.READY; this.rehydrated = true; this.emit(constants.CONNECTOR_EVENTS.ERRORED, error); // track connection events if (shouldTrack) { var _this$analytics3, _this$analytics4; (_this$analytics3 = this.analytics) === null || _this$analytics3 === void 0 || _this$analytics3.track(analytics.ANALYTICS_EVENTS.CONNECTION_STARTED, eventData); (_this$analytics4 = this.analytics) === null || _this$analytics4 === void 0 || _this$analytics4.track(analytics.ANALYTICS_EVENTS.CONNECTION_FAILED, _objectSpread(_objectSpread(_objectSpread({}, eventData), baseControllers.getErrorAnalyticsProperties(error)), {}, { duration: Date.now() - startTime })); } const finalError = error instanceof index$1.Web3AuthError ? error : index$1.WalletLoginError.connectionError(`Failed to login with wallet connect: ${(error === null || error === void 0 ? void 0 : error.message) || ""}`, error); throw finalError; } } async switchChain(params, init = false) { super.checkSwitchChainRequirements(params, init); if (!this.wcProvider) throw index$1.WalletInitializationError.notReady("Wallet Connect provider is not ready yet"); try { await this.wcProvider.switchChain({ chainId: params.chainId }); } catch (error) { loglevel.log.error("error while switching chain", error); throw error; } } async getUserInfo() { if (!this.canAuthorize) throw index$1.WalletLoginError.notConnectedError("Not connected with wallet, Please login/connect first"); return {}; } async disconnect(options = { cleanup: false, sessionRemovedByWallet: false }) { var _this$activeSession2, _this$activeSession3; const { cleanup } = options; if (!this.connector || !this.connected || !((_this$activeSession2 = this.activeSession) !== null && _this$activeSession2 !== void 0 && _this$activeSession2.topic)) throw index$1.WalletLoginError.notConnectedError("Not connected with wallet"); if (!options.sessionRemovedByWallet) await this.connector.disconnect({ topic: (_this$activeSession3 = this.activeSession) === null || _this$activeSession3 === void 0 ? void 0 : _this$activeSession3.topic, reason: utils$1.getSdkError("USER_DISCONNECTED") }); await this.clearWalletSession(); this.rehydrated = false; this._solanaWallet = null; if (cleanup) { this.connector = null; this.status = constants.CONNECTOR_STATUS.NOT_READY; this.wcProvider = null; } else { // ready to connect again this.status = constants.CONNECTOR_STATUS.READY; } this.activeSession = null; this.emit(constants.CONNECTOR_EVENTS.DISCONNECTED, { connector: index.WALLET_CONNECTORS.WALLET_CONNECT_V2 }); } async getAuthTokenInfo() { if (!this.provider || !this.canAuthorize) throw index$1.WalletLoginError.notConnectedError(); this.status = constants.CONNECTOR_STATUS.AUTHORIZING; this.emit(constants.CONNECTOR_EVENTS.AUTHORIZING, { connector: index.WALLET_CONNECTORS.WALLET_CONNECT_V2 }); const { chainId } = this.provider; const currentChainConfig = this.coreOptions.chains.find(x => x.chainId === chainId); if (!currentChainConfig) throw index$1.WalletLoginError.connectionError("Chain config is not available"); const { chainNamespace } = currentChainConfig; const accounts = chainNamespace === baseControllers.CHAIN_NAMESPACES.SOLANA && this._solanaWallet ? this._solanaWallet.accounts.map(a => a.address) : await this.provider.request({ method: wsEmbed.EVM_METHOD_TYPES.GET_ACCOUNTS }); if (accounts && accounts.length > 0) { const cached = await this.getCachedOrNullAuthTokenInfo(accounts[0]); if (cached) return cached; const authServer = utils.citadelServerUrl(this.coreOptions.authBuildEnv); const { challenge, signature } = await this.generateChallengeAndSign(authServer, accounts); return this.verifyAndAuthorize({ chainNamespace, signedMessage: signature, challenge, authServer }); } throw index$1.WalletLoginError.notConnectedError("Not connected with wallet, Please login/connect first"); } async generateChallengeAndSign(authServerUrl, accounts) { const { chainId } = this.provider; const currentChainConfig = this.coreOptions.chains.find(x => x.chainId === chainId); if (!currentChainConfig) throw index$1.WalletLoginError.connectionError("Chain config is not available"); const { chainNamespace } = currentChainConfig; const accountsToUse = accounts || (chainNamespace === baseControllers.CHAIN_NAMESPACES.SOLANA && this._solanaWallet ? this._solanaWallet.accounts.map(a => a.address) : await this.provider.request({ method: wsEmbed.EVM_METHOD_TYPES.GET_ACCOUNTS })); if (!accountsToUse || accountsToUse.length === 0) { throw index$1.WalletLoginError.notConnectedError("No accounts found in the connected wallet"); } const payload = { domain: window.location.origin, uri: window.location.href, address: accountsToUse[0], chainId: parseInt(chainId, 16), version: "1", nonce: siwe.generateSiweNonce(), issuedAt: new Date().toISOString() }; const authServer = authServerUrl || utils.citadelServerUrl(this.coreOptions.authBuildEnv); const challenge = await baseControllers.signChallenge(payload, chainNamespace, authServer); const signature = await this._getSignedMessage(challenge, accountsToUse, chainNamespace); return { challenge, signature, chainNamespace }; } async enableMFA() { throw new Error("Method Not implemented"); } async manageMFA() { throw new Error("Method Not implemented"); } cleanupPendingPairings() { if (!this.connector) throw index$1.WalletInitializationError.notReady("Wallet connector is not ready yet"); const inactivePairings = this.connector.pairing.getAll({ active: false }); if (!utils$1.isValidArray(inactivePairings)) return; inactivePairings.forEach(pairing => { if (this.connector) { this.connector.pairing.delete(pairing.topic, utils$1.getSdkError("USER_DISCONNECTED")); } }); } async checkForPersistedSession() { if (!this.connector) throw index$1.WalletInitializationError.notReady("Wallet connector is not ready yet"); if (this.connector.session.length) { const lastKeyIndex = this.connector.session.keys.length - 1; this.activeSession = this.connector.session.get(this.connector.session.keys[lastKeyIndex]); } return this.activeSession; } async createNewSession({ forceNewSession = false, chainConfig, trackCompletionEvents, getAuthTokenInfo }) { try { var _this$activeSession4, _this$connectorOption7; if (!this.connector) throw index$1.WalletInitializationError.notReady("Wallet connector is not ready yet"); if (!this.connectorOptions.loginSettings || Object.keys(this.connectorOptions.loginSettings).length === 0) throw index$1.WalletInitializationError.notReady("login settings are not set yet"); this.status = constants.CONNECTOR_STATUS.CONNECTING; this.emit(constants.CONNECTOR_EVENTS.CONNECTING, { connector: index.WALLET_CONNECTORS.WALLET_CONNECT_V2 }); if (forceNewSession && (_this$activeSession4 = this.activeSession) !== null && _this$activeSession4 !== void 0 && _this$activeSession4.topic) { var _this$activeSession5; await this.connector.disconnect({ topic: (_this$activeSession5 = this.activeSession) === null || _this$activeSession5 === void 0 ? void 0 : _this$activeSession5.topic, reason: utils$1.getSdkError("USER_DISCONNECTED") }); } const { uri, approval } = await this.connector.connect(this.connectorOptions.loginSettings); const qrcodeModal = (_this$connectorOption7 = this.connectorOptions) === null || _this$connectorOption7 === void 0 || (_this$connectorOption7 = _this$connectorOption7.connectorSettings) === null || _this$connectorOption7 === void 0 ? void 0 : _this$connectorOption7.qrcodeModal; // Open QRCode modal if a URI was returned (i.e. we're not connecting with an existing pairing). if (uri) { if (qrcodeModal) { try { await qrcodeModal.openModal({ uri }); loglevel.log.debug("EVENT", "QR Code Modal closed"); this.status = constants.CONNECTOR_STATUS.READY; this.emit(constants.CONNECTOR_EVENTS.READY, index.WALLET_CONNECTORS.WALLET_CONNECT_V2); } catch (error) { loglevel.log.error("unable to open qr code modal", error); } } else { this.updateConnectorData({ uri }); } } loglevel.log.info("awaiting session approval from wallet"); // Await session approval from the wallet. const session = await approval(); this.activeSession = session; // Handle the returned session (e.g. update UI to "connected" state). await this.onConnectHandler({ chain: chainConfig, trackCompletionEvents, getAuthTokenInfo }); if (qrcodeModal) { qrcodeModal.closeModal(); } } catch (error) { var _error$message; if ((_error$message = error.message) !== null && _error$message !== void 0 && _error$message.toLowerCase().includes("proposal expired")) { // Retry if connector status is still connecting loglevel.log.info("current connector status: ", this.status); if (this.status === constants.CONNECTOR_STATUS.CONNECTING) { loglevel.log.info("retrying to create new wallet connect session since proposal expired"); return this.createNewSession({ forceNewSession: true, chainConfig, getAuthTokenInfo }); } if (this.status === constants.CONNECTOR_STATUS.READY) { loglevel.log.info("ignoring proposal expired error since some other connector is connected"); return; } } loglevel.log.error("error while creating new wallet connect session", error); this.emit(constants.CONNECTOR_EVENTS.ERRORED, error); throw error; } } async onConnectHandler({ chain, trackCompletionEvents, getAuthTokenInfo }) { var _this$connectorOption8, _this$activeSession$n, _this$activeSession6; if (!this.connector || !this.wcProvider) throw index$1.WalletInitializationError.notReady("Wallet connect connector is not ready yet"); this.subscribeEvents(); if ((_this$connectorOption8 = this.connectorOptions.connectorSettings) !== null && _this$connectorOption8 !== void 0 && _this$connectorOption8.qrcodeModal) { this.wcProvider = new WalletConnectV2Provider.WalletConnectV2Provider({ config: { chain, chains: this.coreOptions.chains, skipLookupNetwork: true }, connector: this.connector }); } await this.wcProvider.setupProvider(this.connector); // Derive the active Solana chain from the WC session namespaces (genesis hash → hex chainId), // rather than picking the first Solana chain from coreOptions which may not match the session. const sessionSolanaChains = (_this$activeSession$n = (_this$activeSession6 = this.activeSession) === null || _this$activeSession6 === void 0 || (_this$activeSession6 = _this$activeSession6.namespaces) === null || _this$activeSession6 === void 0 || (_this$activeSession6 = _this$activeSession6.solana) === null || _this$activeSession6 === void 0 ? void 0 : _this$activeSession6.chains) !== null && _this$activeSession$n !== void 0 ? _this$activeSession$n : []; if (sessionSolanaChains.length > 0) { const reverseMap = Object.fromEntries(Object.entries(constants$1.SOLANA_CAIP_CHAIN_MAP).map(([hex, hash]) => [hash, hex])); const genesisHash = sessionSolanaChains[0].split(":")[1]; const hexChainId = reverseMap[genesisHash]; const solanaChain = hexChainId ? this.coreOptions.chains.find(c => c.chainId === hexChainId) : null; if (solanaChain) { this._solanaWallet = await wcSolanaWallet.WCSolanaWallet.create(this.connector, solanaChain); } } this.cleanupPendingPairings(); this.status = constants.CONNECTOR_STATUS.CONNECTED; // track connection events if (trackCompletionEvents) trackCompletionEvents(); this.emit(constants.CONNECTOR_EVENTS.CONNECTED, { connectorName: index.WALLET_CONNECTORS.WALLET_CONNECT_V2, reconnected: this.rehydrated, ethereumProvider: this.provider, solanaWallet: this._solanaWallet, connectorNamespace: this.connectorNamespace }); await this.authorizeOrDisconnect(getAuthTokenInfo); } subscribeEvents() { if (!this.connector) throw index$1.WalletInitializationError.notReady("Wallet connector is not ready yet"); this.connector.events.on("session_update", ({ topic, params }) => { if (!this.connector) return; const { namespaces } = params; const _session = this.connector.session.get(topic); // Overwrite the `namespaces` of the existing session with the incoming one. const updatedSession = _objectSpread(_objectSpread({}, _session), {}, { namespaces }); // Integrate the updated session state into your dapp state. this.activeSession = updatedSession; }); this.connector.events.on("session_delete", () => { // Session was deleted -> reset the dapp state, clean up from user session, etc. this.disconnect({ sessionRemovedByWallet: true }); }); this.connector.events.on("session_expire", ({ topic }) => { var _this$activeSession7; // Session has expired -> clean up the session loglevel.log.info("Session expired event received for topic:", topic); if (((_this$activeSession7 = this.activeSession) === null || _this$activeSession7 === void 0 ? void 0 : _this$activeSession7.topic) === topic) { this.disconnect({ sessionRemovedByWallet: true }).catch(error => { loglevel.log.error("Failed to disconnect expired session", error); }); } }); } async _getSignedMessage(challenge, accounts, chainNamespace) { if (chainNamespace === baseControllers.CHAIN_NAMESPACES.SOLANA && this._solanaWallet) { return solana.walletSignMessage(this._solanaWallet, challenge, accounts[0]); } return this.provider.request({ method: wsEmbed.EVM_METHOD_TYPES.PERSONAL_SIGN, params: [challenge, accounts[0]] }); } } const walletConnectV2Connector = params => { return ({ coreOptions, projectConfig, analytics }) => { var _params$walletConnect; const projectId = (params === null || params === void 0 || (_params$walletConnect = params.walletConnectInitOptions) === null || _params$walletConnect === void 0 ? void 0 : _params$walletConnect.projectId) || (projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.walletConnectProjectId); const connectorSettings = _objectSpread(_objectSpread({}, params), {}, { walletConnectInitOptions: _objectSpread(_objectSpread({}, params === null || params === void 0 ? void 0 : params.walletConnectInitOptions), {}, { projectId: projectId }) }); return new WalletConnectV2Connector({ connectorSettings, coreOptions, analytics }); }; }; exports.walletConnectV2Connector = walletConnectV2Connector;