UNPKG

@biuauth/wallet-connect-v2-adapter

Version:
481 lines (476 loc) 20 kB
import { CHAIN_NAMESPACES, WALLET_ADAPTERS, ADAPTER_NAMESPACES, ADAPTER_CATEGORY, ADAPTER_STATUS, WalletInitializationError, ADAPTER_EVENTS, log, Web3AuthError, WalletLoginError, WalletOperationsError } from '@web3auth/base'; import _defineProperty from '@babel/runtime/helpers/defineProperty'; import { WalletConnectV2Provider } from '@biuauth/ethereum-provider'; import SignClient from '@walletconnect/sign-client'; import { getSdkError, isValidArray } from '@walletconnect/utils'; import { BaseEvmAdapter } from '@web3auth/base-evm-adapter'; import merge from 'lodash.merge'; const WALLET_CONNECT_EXTENSION_ADAPTERS = [{ name: "Argent", chains: [CHAIN_NAMESPACES.EIP155], logo: "https://images.web3auth.io/login-argent.svg", mobile: { native: "argent://", universal: "https://www.argent.xyz/app" }, desktop: { native: "", universal: "" } }, { name: "Trust Wallet", chains: [CHAIN_NAMESPACES.EIP155], logo: "https://images.web3auth.io/login-trust.svg", mobile: { native: "trust:", universal: "https://link.trustwallet.com" }, desktop: { native: "", universal: "" } }, { name: "Zerion", chains: [CHAIN_NAMESPACES.EIP155], logo: "https://images.web3auth.io/login-zerion.svg", mobile: { native: "zerion://", universal: "https://wallet.zerion.io" }, desktop: { native: "", universal: "" } }]; var DEFAULT_EIP155_METHODS; (function (DEFAULT_EIP155_METHODS) { DEFAULT_EIP155_METHODS["ETH_SEND_TRANSACTION"] = "eth_sendTransaction"; DEFAULT_EIP155_METHODS["ETH_SIGN"] = "eth_sign"; DEFAULT_EIP155_METHODS["PERSONAL_SIGN"] = "personal_sign"; DEFAULT_EIP155_METHODS["ETH_SIGN_TYPED_DATA"] = "eth_signTypedData"; })(DEFAULT_EIP155_METHODS || (DEFAULT_EIP155_METHODS = {})); var DEFAULT_EIP_155_EVENTS; (function (DEFAULT_EIP_155_EVENTS) { DEFAULT_EIP_155_EVENTS["ETH_CHAIN_CHANGED"] = "chainChanged"; DEFAULT_EIP_155_EVENTS["ETH_ACCOUNTS_CHANGED"] = "accountsChanged"; })(DEFAULT_EIP_155_EVENTS || (DEFAULT_EIP_155_EVENTS = {})); /** * Extracts a name for the site from the DOM */ const getSiteName = window => { const { document } = window; const siteName = document.querySelector('head > meta[property="og:site_name"]'); if (siteName) { return siteName.content; } const metaTitle = document.querySelector('head > meta[name="title"]'); if (metaTitle) { return metaTitle.content; } if (document.title && document.title.length > 0) { return document.title; } return window.location.hostname; }; /** * Returns whether the given image URL exists * @param url - the url of the image * @returns - whether the image exists */ function imgExists(url) { return new Promise((resolve, reject) => { try { const img = document.createElement("img"); img.onload = () => resolve(true); img.onerror = () => resolve(false); img.src = url; } catch (e) { reject(e); } }); } /** * Extracts an icon for the site from the DOM */ async function getSiteIcon(window) { const { document } = window; // Use the site's favicon if it exists let icon = document.querySelector('head > link[rel="shortcut icon"]'); if (icon && (await imgExists(icon.href))) { return icon.href; } // Search through available icons in no particular order icon = Array.from(document.querySelectorAll('head > link[rel="icon"]')).find(_icon => Boolean(_icon.href)) || null; if (icon && (await imgExists(icon.href))) { return icon.href; } return null; } /** * Gets site metadata and returns it * */ const getSiteMetadata = async () => ({ name: getSiteName(window), icon: await getSiteIcon(window) }); const getNamespacesFromChains = chains => { const supportedNamespaces = []; chains.forEach(chainId => { const [namespace] = chainId.split(":"); if (!supportedNamespaces.includes(namespace)) { supportedNamespaces.push(namespace); } }); return supportedNamespaces; }; const getSupportedMethodsByNamespace = namespace => { switch (namespace) { case CHAIN_NAMESPACES.EIP155: return Object.values(DEFAULT_EIP155_METHODS); default: throw new Error(`No default methods for namespace: ${namespace}`); } }; const getSupportedEventsByNamespace = namespace => { switch (namespace) { case CHAIN_NAMESPACES.EIP155: return Object.values(DEFAULT_EIP_155_EVENTS); default: throw new Error(`No default events for namespace: ${namespace}`); } }; const getRequiredNamespaces = chains => { const selectedNamespaces = getNamespacesFromChains(chains); return Object.fromEntries(selectedNamespaces.map(namespace => [namespace, { methods: getSupportedMethodsByNamespace(namespace), chains: chains.filter(chain => chain.startsWith(namespace)), events: getSupportedEventsByNamespace(namespace) }])); }; const getWalletConnectV2Settings = async (namespace, chainIds, projectID) => { if (namespace === CHAIN_NAMESPACES.EIP155) { const appMetadata = await getSiteMetadata(); const adapterSettings = { walletConnectInitOptions: { projectId: projectID, relayUrl: "wss://relay.walletconnect.com", metadata: { name: appMetadata.name, description: appMetadata.name, url: window.location.origin, icons: [appMetadata.icon || ""] } } }; const chainNamespaces = chainIds.map(chainId => { return `${namespace}:${chainId}`; }); const loginSettings = { requiredNamespaces: getRequiredNamespaces(chainNamespaces) }; return { adapterSettings, loginSettings }; } throw new Error(`Unsupported chain namespace: ${namespace}`); }; const isChainIdSupported = (chainNamespace, chainID, loginSettings) => { var _supportedNamespaces$, _supportedNamespaces$2; const supportedNamespaces = (loginSettings === null || loginSettings === void 0 ? void 0 : loginSettings.requiredNamespaces) || {}; const wcChainNamespace = `${chainNamespace}:${chainID}`; if (!supportedNamespaces[chainNamespace].chains || ((_supportedNamespaces$ = supportedNamespaces[chainNamespace].chains) === null || _supportedNamespaces$ === void 0 ? void 0 : _supportedNamespaces$.length) === 0) { return false; } const isSupported = (_supportedNamespaces$2 = supportedNamespaces[chainNamespace].chains) === null || _supportedNamespaces$2 === void 0 ? void 0 : _supportedNamespaces$2.includes(wcChainNamespace); return !!isSupported; }; function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } class WalletConnectV2Adapter extends BaseEvmAdapter { constructor() { let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; super(options); _defineProperty(this, "name", WALLET_ADAPTERS.WALLET_CONNECT_V2); _defineProperty(this, "adapterNamespace", ADAPTER_NAMESPACES.EIP155); _defineProperty(this, "currentChainNamespace", CHAIN_NAMESPACES.EIP155); _defineProperty(this, "type", ADAPTER_CATEGORY.EXTERNAL); _defineProperty(this, "adapterOptions", void 0); _defineProperty(this, "status", ADAPTER_STATUS.NOT_READY); _defineProperty(this, "adapterData", { uri: "", extensionAdapters: WALLET_CONNECT_EXTENSION_ADAPTERS }); _defineProperty(this, "connector", null); _defineProperty(this, "activeSession", null); _defineProperty(this, "wcProvider", null); this.adapterOptions = _objectSpread({}, options); } get connected() { return !!this.activeSession; } get provider() { if (this.status !== ADAPTER_STATUS.NOT_READY && this.wcProvider) { return this.wcProvider.provider; } return null; } set provider(_) { throw new Error("Not implemented"); } async init(options) { var _this$adapterOptions$, _this$chainConfig, _this$chainConfig2; await super.init(); 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, [parseInt((_this$chainConfig2 = this.chainConfig) === null || _this$chainConfig2 === void 0 ? void 0 : _this$chainConfig2.chainId, 16)], projectId); if (!this.adapterOptions.loginSettings) { this.adapterOptions.loginSettings = wc2Settings.loginSettings; } this.adapterOptions.adapterSettings = merge(wc2Settings.adapterSettings, this.adapterOptions.adapterSettings); const { adapterSettings } = this.adapterOptions; this.connector = await SignClient.init(adapterSettings === null || adapterSettings === void 0 ? void 0 : adapterSettings.walletConnectInitOptions); this.wcProvider = new WalletConnectV2Provider({ 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) || ""}`); throw finalError; } } async addChain(chainConfig) { var _this$wcProvider; let init = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; super.checkAddChainRequirements(chainConfig, init); if (!isChainIdSupported(this.currentChainNamespace, parseInt(chainConfig.chainId, 16), this.adapterOptions.loginSettings)) { throw WalletOperationsError.chainIDNotAllowed(`Unsupported chainID: ${chainConfig.chainId}`); } await ((_this$wcProvider = this.wcProvider) === null || _this$wcProvider === void 0 ? void 0 : _this$wcProvider.addChain(chainConfig)); this.addChainConfig(chainConfig); } async switchChain(params) { var _this$wcProvider2; let init = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; super.checkSwitchChainRequirements(params, init); if (!isChainIdSupported(this.currentChainNamespace, parseInt(params.chainId, 16), this.adapterOptions.loginSettings)) { throw WalletOperationsError.chainIDNotAllowed(`Unsupported chainID: ${params.chainId}`); } 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() { var _this$activeSession, _this$activeSession2; let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { cleanup: false }; await super.disconnectSession(); 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"); 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; await super.disconnect(); } 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() { let opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { forceNewSession: false }; try { var _this$activeSession3, _this$adapterOptions; if (!this.connector) throw WalletInitializationError.notReady("Wallet adapter is not ready yet"); if (!this.adapterOptions.loginSettings) 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") }); } log.debug("creating new session for web3auth wallet connect"); const { uri, approval } = await this.connector.connect(this.adapterOptions.loginSettings); const qrcodeModal = (_this$adapterOptions = this.adapterOptions) === null || _this$adapterOptions === void 0 || (_this$adapterOptions = _this$adapterOptions.adapterSettings) === null || _this$adapterOptions === void 0 ? void 0 : _this$adapterOptions.qrcodeModal; // Open QRCode modal if a URI was returned (i.e. we're not connecting with an existing pairing). if (uri) { if (qrcodeModal) { 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); } else { this.updateAdapterData({ uri, extensionAdapters: WALLET_CONNECT_EXTENSION_ADAPTERS }); } } this.connector.events.once("proposal_expire", args => { log.info("proposal expired", args); // Handle proposal expiration this.createNewSession({ forceNewSession: true }); }); 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) { log.error("error while creating new wallet connect session", error); this.emit(ADAPTER_EVENTS.ERRORED, error); throw error; } } async onConnectHandler() { var _this$adapterOptions$2; 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"); if ((_this$adapterOptions$2 = this.adapterOptions.adapterSettings) !== null && _this$adapterOptions$2 !== void 0 && _this$adapterOptions$2.qrcodeModal) { this.wcProvider = new WalletConnectV2Provider({ config: { chainConfig: this.chainConfig, skipLookupNetwork: true }, connector: this.connector }); } await this.wcProvider.setupProvider(this.connector); this.subscribeEvents(); this.cleanupPendingPairings(); this.status = ADAPTER_STATUS.CONNECTED; this.emit(ADAPTER_EVENTS.CONNECTED, { adapter: WALLET_ADAPTERS.WALLET_CONNECT_V2, reconnected: this.rehydrated }); } subscribeEvents() { if (!this.connector) throw WalletInitializationError.notReady("Wallet adapter is not ready yet"); this.connector.events.on("session_update", _ref => { let { topic, params } = _ref; 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(); }); } } export { DEFAULT_EIP155_METHODS, DEFAULT_EIP_155_EVENTS, WALLET_CONNECT_EXTENSION_ADAPTERS, WalletConnectV2Adapter, getNamespacesFromChains, getRequiredNamespaces, getSupportedEventsByNamespace, getSupportedMethodsByNamespace, getWalletConnectV2Settings }; //# sourceMappingURL=walletConnectV2Adapter.esm.js.map