UNPKG

@web3auth/wallet-connect-v2-adapter

Version:
371 lines (366 loc) 16.8 kB
import _objectSpread from '@babel/runtime/helpers/objectSpread2'; import _defineProperty from '@babel/runtime/helpers/defineProperty'; import { signChallenge, verifySignedChallenge } from '@toruslabs/base-controllers'; import Client from '@walletconnect/sign-client'; import { getSdkError, isValidArray } from '@walletconnect/utils'; import { BaseAdapter, WALLET_ADAPTERS, ADAPTER_NAMESPACES, CHAIN_NAMESPACES, ADAPTER_CATEGORY, ADAPTER_STATUS, WalletInitializationError, ADAPTER_EVENTS, log, Web3AuthError, WalletLoginError, getSavedToken, checkIfTokenIsExpired, saveToken } from '@web3auth/base'; import base58 from 'bs58'; import deepmerge from 'deepmerge'; import { getWalletConnectV2Settings } from './config.js'; import { WalletConnectV2Provider } from './WalletConnectV2Provider.js'; class WalletConnectV2Adapter extends BaseAdapter { constructor(options = {}) { super(options); _defineProperty(this, "name", WALLET_ADAPTERS.WALLET_CONNECT_V2); _defineProperty(this, "adapterNamespace", ADAPTER_NAMESPACES.MULTICHAIN); _defineProperty(this, "currentChainNamespace", CHAIN_NAMESPACES.OTHER); _defineProperty(this, "type", ADAPTER_CATEGORY.EXTERNAL); _defineProperty(this, "adapterOptions", {}); _defineProperty(this, "status", ADAPTER_STATUS.NOT_READY); _defineProperty(this, "adapterData", { uri: "" }); _defineProperty(this, "connector", null); _defineProperty(this, "activeSession", null); _defineProperty(this, "wcProvider", null); this.adapterOptions = _objectSpread({}, options); this.setAdapterSettings(options); } get connected() { return !!this.activeSession; } get provider() { if (this.status !== ADAPTER_STATUS.NOT_READY && this.wcProvider) { return this.wcProvider; } return null; } set provider(_) { throw new Error("Not implemented"); } async init(options) { var _this$adapterOptions$, _this$chainConfig, _this$chainConfig2; super.checkInitializationRequirements(); const projectId = (_this$adapterOptions$ = this.adapterOptions.adapterSettings) === null || _this$adapterOptions$ === void 0 || (_this$adapterOptions$ = _this$adapterOptions$.walletConnectInitOptions) === null || _this$adapterOptions$ === void 0 ? void 0 : _this$adapterOptions$.projectId; if (!projectId) { throw WalletInitializationError.invalidParams("Wallet connect project id is required in wallet connect v2 adapter"); } const wc2Settings = await getWalletConnectV2Settings((_this$chainConfig = this.chainConfig) === null || _this$chainConfig === void 0 ? void 0 : _this$chainConfig.chainNamespace, [(_this$chainConfig2 = this.chainConfig) === null || _this$chainConfig2 === void 0 ? void 0 : _this$chainConfig2.chainId], projectId); if (!this.adapterOptions.loginSettings || Object.keys(this.adapterOptions.loginSettings).length === 0) { this.adapterOptions.loginSettings = wc2Settings.loginSettings; } this.adapterOptions.adapterSettings = deepmerge(wc2Settings.adapterSettings || {}, this.adapterOptions.adapterSettings || {}); const { adapterSettings } = this.adapterOptions; this.connector = await Client.init(adapterSettings === null || adapterSettings === void 0 ? void 0 : adapterSettings.walletConnectInitOptions); this.wcProvider = new WalletConnectV2Provider({ clientId: this.clientId, config: { chainConfig: this.chainConfig }, connector: this.connector }); this.emit(ADAPTER_EVENTS.READY, WALLET_ADAPTERS.WALLET_CONNECT_V2); this.status = ADAPTER_STATUS.READY; log.debug("initializing wallet connect v2 adapter"); if (options.autoConnect) { await this.checkForPersistedSession(); if (this.connected) { this.rehydrated = true; try { await this.onConnectHandler(); } catch (error) { log.error("wallet auto connect", error); this.emit(ADAPTER_EVENTS.ERRORED, error); } } else { this.status = ADAPTER_STATUS.NOT_READY; this.emit(ADAPTER_EVENTS.CACHE_CLEAR); } } } async connect() { super.checkConnectionRequirements(); if (!this.connector) throw WalletInitializationError.notReady("Wallet adapter is not ready yet"); try { // if already connected if (this.connected) { await this.onConnectHandler(); return this.provider; } if (this.status !== ADAPTER_STATUS.CONNECTING) { await this.createNewSession(); } return this.provider; } catch (error) { log.error("Wallet connect v2 adapter error while connecting", error); // ready again to be connected this.status = ADAPTER_STATUS.READY; this.rehydrated = true; this.emit(ADAPTER_EVENTS.ERRORED, error); const finalError = error instanceof Web3AuthError ? error : WalletLoginError.connectionError(`Failed to login with wallet connect: ${(error === null || error === void 0 ? void 0 : error.message) || ""}`, error); throw finalError; } } // should be called only before initialization. setAdapterSettings(adapterSettings) { var _this$adapterOptions$2, _this$adapterOptions, _this$adapterOptions$3, _this$adapterOptions2, _this$adapterOptions$4; super.setAdapterSettings(adapterSettings); const { qrcodeModal, walletConnectInitOptions } = (adapterSettings === null || adapterSettings === void 0 ? void 0 : adapterSettings.adapterSettings) || {}; this.adapterOptions = _objectSpread(_objectSpread({}, this.adapterOptions), {}, { adapterSettings: (_this$adapterOptions$2 = (_this$adapterOptions = this.adapterOptions) === null || _this$adapterOptions === void 0 ? void 0 : _this$adapterOptions.adapterSettings) !== null && _this$adapterOptions$2 !== void 0 ? _this$adapterOptions$2 : {}, loginSettings: (_this$adapterOptions$3 = (_this$adapterOptions2 = this.adapterOptions) === null || _this$adapterOptions2 === void 0 ? void 0 : _this$adapterOptions2.loginSettings) !== null && _this$adapterOptions$3 !== void 0 ? _this$adapterOptions$3 : {} }); if (qrcodeModal) this.adapterOptions.adapterSettings.qrcodeModal = qrcodeModal; if (walletConnectInitOptions) this.adapterOptions.adapterSettings.walletConnectInitOptions = _objectSpread(_objectSpread({}, (_this$adapterOptions$4 = this.adapterOptions.adapterSettings.walletConnectInitOptions) !== null && _this$adapterOptions$4 !== void 0 ? _this$adapterOptions$4 : {}), walletConnectInitOptions); const { loginSettings } = adapterSettings; if (loginSettings) this.adapterOptions.loginSettings = _objectSpread(_objectSpread({}, this.adapterOptions.loginSettings || {}), loginSettings); } async addChain(chainConfig, init = false) { var _this$wcProvider; super.checkAddChainRequirements(chainConfig, init); await ((_this$wcProvider = this.wcProvider) === null || _this$wcProvider === void 0 ? void 0 : _this$wcProvider.addChain(chainConfig)); this.addChainConfig(chainConfig); } async switchChain(params, init = false) { var _this$wcProvider2; super.checkSwitchChainRequirements(params, init); await ((_this$wcProvider2 = this.wcProvider) === null || _this$wcProvider2 === void 0 ? void 0 : _this$wcProvider2.switchChain({ chainId: params.chainId })); this.setAdapterSettings({ chainConfig: this.getChainConfig(params.chainId) }); } async getUserInfo() { if (!this.connected) throw WalletLoginError.notConnectedError("Not connected with wallet, Please login/connect first"); return {}; } async disconnect(options = { cleanup: false, sessionRemovedByWallet: false }) { var _this$activeSession, _this$activeSession2; const { cleanup } = options; if (!this.connector || !this.connected || !((_this$activeSession = this.activeSession) !== null && _this$activeSession !== void 0 && _this$activeSession.topic)) throw WalletLoginError.notConnectedError("Not connected with wallet"); if (!options.sessionRemovedByWallet) await this.connector.disconnect({ topic: (_this$activeSession2 = this.activeSession) === null || _this$activeSession2 === void 0 ? void 0 : _this$activeSession2.topic, reason: getSdkError("USER_DISCONNECTED") }); this.rehydrated = false; if (cleanup) { this.connector = null; this.status = ADAPTER_STATUS.NOT_READY; this.wcProvider = null; } else { // ready to connect again this.status = ADAPTER_STATUS.READY; } this.activeSession = null; this.emit(ADAPTER_EVENTS.DISCONNECTED); } async authenticateUser() { if (!this.provider || this.status !== ADAPTER_STATUS.CONNECTED) throw WalletLoginError.notConnectedError(); const { chainNamespace, chainId } = this.chainConfig; const accounts = await this.provider.request({ method: chainNamespace === CHAIN_NAMESPACES.EIP155 ? "eth_accounts" : "getAccounts" }); if (accounts && accounts.length > 0) { const existingToken = getSavedToken(accounts[0], this.name); if (existingToken) { const isExpired = checkIfTokenIsExpired(existingToken); if (!isExpired) { return { idToken: existingToken }; } } const payload = { domain: window.location.origin, uri: window.location.href, address: accounts[0], chainId: parseInt(chainId, 16), version: "1", nonce: Math.random().toString(36).slice(2), issuedAt: new Date().toISOString() }; const challenge = await signChallenge(payload, chainNamespace); const signedMessage = await this._getSignedMessage(challenge, accounts, chainNamespace); const idToken = await verifySignedChallenge(chainNamespace, signedMessage, challenge, this.name, this.sessionTime, this.clientId, this.web3AuthNetwork); saveToken(accounts[0], this.name, idToken); return { idToken }; } throw WalletLoginError.notConnectedError("Not connected with wallet, Please login/connect first"); } async enableMFA() { throw new Error("Method Not implemented"); } async manageMFA() { throw new Error("Method Not implemented"); } cleanupPendingPairings() { if (!this.connector) throw WalletInitializationError.notReady("Wallet adapter is not ready yet"); const inactivePairings = this.connector.pairing.getAll({ active: false }); if (!isValidArray(inactivePairings)) return; inactivePairings.forEach(pairing => { if (this.connector) { this.connector.pairing.delete(pairing.topic, getSdkError("USER_DISCONNECTED")); } }); } async checkForPersistedSession() { if (!this.connector) throw WalletInitializationError.notReady("Wallet adapter 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(opts = { forceNewSession: false }) { try { var _this$activeSession3, _this$adapterOptions3; if (!this.connector) throw WalletInitializationError.notReady("Wallet adapter is not ready yet"); if (!this.adapterOptions.loginSettings || Object.keys(this.adapterOptions.loginSettings).length === 0) throw WalletInitializationError.notReady("login settings are not set yet"); this.status = ADAPTER_STATUS.CONNECTING; this.emit(ADAPTER_EVENTS.CONNECTING, { adapter: WALLET_ADAPTERS.WALLET_CONNECT_V2 }); if (opts.forceNewSession && (_this$activeSession3 = this.activeSession) !== null && _this$activeSession3 !== void 0 && _this$activeSession3.topic) { var _this$activeSession4; await this.connector.disconnect({ topic: (_this$activeSession4 = this.activeSession) === null || _this$activeSession4 === void 0 ? void 0 : _this$activeSession4.topic, reason: getSdkError("USER_DISCONNECTED") }); } const { uri, approval } = await this.connector.connect(this.adapterOptions.loginSettings); const qrcodeModal = (_this$adapterOptions3 = this.adapterOptions) === null || _this$adapterOptions3 === void 0 || (_this$adapterOptions3 = _this$adapterOptions3.adapterSettings) === null || _this$adapterOptions3 === void 0 ? void 0 : _this$adapterOptions3.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 }); log.debug("EVENT", "QR Code Modal closed"); this.status = ADAPTER_STATUS.READY; this.emit(ADAPTER_EVENTS.READY, WALLET_ADAPTERS.WALLET_CONNECT_V2); } catch (error) { log.error("unable to open qr code modal"); } } else { this.updateAdapterData({ uri }); } } 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(); if (qrcodeModal) { qrcodeModal.closeModal(); } } catch (error) { var _message; if ((_message = error.message) !== null && _message !== void 0 && _message.toLowerCase().includes("proposal expired")) { // Retry if adapter status is still connecting log.info("current adapter status: ", this.status); if (this.status === ADAPTER_STATUS.CONNECTING) { log.info("retrying to create new wallet connect session since proposal expired"); return this.createNewSession({ forceNewSession: true }); } if (this.status === ADAPTER_STATUS.READY) { log.info("ignoring proposal expired error since some other adapter is connected"); return; } } log.error("error while creating new wallet connect session", error); this.emit(ADAPTER_EVENTS.ERRORED, error); throw error; } } async onConnectHandler() { var _this$adapterOptions$5; if (!this.connector || !this.wcProvider) throw WalletInitializationError.notReady("Wallet adapter is not ready yet"); if (!this.chainConfig) throw WalletInitializationError.invalidParams("Chain config is not set"); this.subscribeEvents(); if ((_this$adapterOptions$5 = this.adapterOptions.adapterSettings) !== null && _this$adapterOptions$5 !== void 0 && _this$adapterOptions$5.qrcodeModal) { this.wcProvider = new WalletConnectV2Provider({ clientId: this.clientId, config: { chainConfig: this.chainConfig, skipLookupNetwork: true }, connector: this.connector }); } await this.wcProvider.setupProvider(this.connector); this.cleanupPendingPairings(); this.status = ADAPTER_STATUS.CONNECTED; this.emit(ADAPTER_EVENTS.CONNECTED, { adapter: WALLET_ADAPTERS.WALLET_CONNECT_V2, reconnected: this.rehydrated, provider: this.provider }); } subscribeEvents() { if (!this.connector) throw WalletInitializationError.notReady("Wallet adapter 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 }); }); } async _getSignedMessage(challenge, accounts, chainNamespace) { const signedMessage = await this.provider.request({ method: chainNamespace === CHAIN_NAMESPACES.EIP155 ? "personal_sign" : "signMessage", params: chainNamespace === CHAIN_NAMESPACES.EIP155 ? [challenge, accounts[0]] : { message: Buffer.from(challenge) } }); if (chainNamespace === CHAIN_NAMESPACES.SOLANA) return base58.encode(signedMessage); return signedMessage; } } export { WalletConnectV2Adapter };