UNPKG

@thirdweb-dev/wallets

Version:

<p align="center"> <br /> <a href="https://thirdweb.com"><img src="https://github.com/thirdweb-dev/js/blob/main/legacy_packages/sdk/logo.svg?raw=true" width="200" alt=""/></a> <br /> </p> <h1 align="center">thirdweb Wallet SDK</h1> <p align="center"> <a h

422 lines (412 loc) • 14.4 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var defineProperty = require('../../../../dist/defineProperty-9051a5d3.cjs.dev.js'); var errors = require('../../../../dist/errors-a8e8ea7b.cjs.dev.js'); var ethers = require('ethers'); var walletIds = require('../../../../dist/walletIds-a0be5020.cjs.dev.js'); var url = require('../../../../dist/url-4b641c31.cjs.dev.js'); var WagmiConnector = require('../../../../dist/WagmiConnector-6ff7d562.cjs.dev.js'); require('@thirdweb-dev/chains'); require('eventemitter3'); function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n["default"] = e; return Object.freeze(n); } const chainsToRequest = new Set([1, 137, 10, 42161, 56]); const NAMESPACE = "eip155"; const REQUESTED_CHAINS_KEY = "wagmi.requestedChains"; const ADD_ETH_CHAIN_METHOD = "wallet_addEthereumChain"; const LAST_USED_CHAIN_ID = "last-used-chain-id"; class WalletConnectConnector extends WagmiConnector.WagmiConnector { constructor(config) { super({ ...config, options: { isNewChainsStale: true, ...config.options } }); defineProperty._defineProperty(this, "id", walletIds.walletIds.walletConnect); defineProperty._defineProperty(this, "name", "WalletConnect"); defineProperty._defineProperty(this, "ready", true); defineProperty._defineProperty(this, "onAccountsChanged", accounts => { if (accounts.length === 0) { this.emit("disconnect"); } else { if (accounts[0]) { this.emit("change", { account: ethers.utils.getAddress(accounts[0]) }); } } }); defineProperty._defineProperty(this, "onChainChanged", async chainId => { const id = Number(chainId); const unsupported = this.isChainUnsupported(id); await this._storage.setItem(LAST_USED_CHAIN_ID, String(chainId)); this.emit("change", { chain: { id, unsupported } }); }); defineProperty._defineProperty(this, "onDisconnect", async () => { await this._setRequestedChainsIds([]); await this._storage.removeItem(LAST_USED_CHAIN_ID); this.emit("disconnect"); }); defineProperty._defineProperty(this, "onDisplayUri", uri => { this.emit("message", { type: "display_uri", data: uri }); }); defineProperty._defineProperty(this, "onConnect", () => { this.emit("connect", { provider: this._provider }); }); this._storage = config.options.storage; this._createProvider(); this.filteredChains = this.chains.length > 50 ? this.chains.filter(c => { return chainsToRequest.has(c.chainId); }) : this.chains; this.showWalletConnectModal = this.options.qrcode !== false; } async connect() { let { chainId: chainIdP, pairingTopic } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; try { let targetChainId = chainIdP; if (!targetChainId) { const lastUsedChainIdStr = await this._storage.getItem(LAST_USED_CHAIN_ID); const lastUsedChainId = lastUsedChainIdStr ? parseInt(lastUsedChainIdStr) : undefined; if (lastUsedChainId && !this.isChainUnsupported(lastUsedChainId)) { targetChainId = lastUsedChainId; } else { targetChainId = this.filteredChains[0]?.chainId; } } if (!targetChainId) { throw new Error("No chains found on connector."); } const provider = await this.getProvider(); this.setupListeners(); if (provider.session) { await provider.disconnect(); const optionalChains = this.filteredChains.filter(chain => chain.chainId !== targetChainId).map(optionalChain => optionalChain.chainId); this.emit("message", { type: "connecting" }); await provider.connect({ pairingTopic, chains: [targetChainId], optionalChains: optionalChains.length > 0 ? optionalChains : [targetChainId] }); await this._setRequestedChainsIds(this.filteredChains.map(_ref => { let { chainId } = _ref; return chainId; })); } // If session exists and chains are authorized, enable provider for required chain const accounts = await provider.enable(); if (!accounts[0]) { throw new Error("No accounts found on provider."); } const account = ethers.utils.getAddress(accounts[0]); const id = await this.getChainId(); const unsupported = this.isChainUnsupported(id); return { account, chain: { id, unsupported }, provider: new ethers.providers.Web3Provider(provider) }; } catch (error) { if (/user rejected/i.test(error?.message)) { throw new errors.UserRejectedRequestError(error); } throw error; } } async disconnect() { const cleanup = () => { if (typeof localStorage === "undefined") { return; } for (const key in localStorage) { if (key.startsWith("wc@2")) { localStorage.removeItem(key); } } }; cleanup(); const provider = await this.getProvider(); const disconnectProvider = async () => { try { await provider.disconnect(); } catch (error) { if (!/No matching key/i.test(error.message)) { throw error; } } finally { this._removeListeners(); await this._setRequestedChainsIds([]); cleanup(); } }; disconnectProvider(); } async getAccount() { const { accounts } = await this.getProvider(); if (!accounts[0]) { throw new Error("No accounts found on provider."); } return ethers.utils.getAddress(accounts[0]); } async getChainId() { const { chainId } = await this.getProvider(); return chainId; } async getProvider() { let { chainId } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; if (!this._provider) { await this._createProvider(); } if (chainId) { await this.switchChain(chainId); } if (!this._provider) { throw new Error("No provider found."); } return this._provider; } async getSigner() { let { chainId } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; const [provider, account] = await Promise.all([this.getProvider({ chainId }), this.getAccount()]); return new ethers.providers.Web3Provider(provider, chainId).getSigner(account); } async isAuthorized() { try { const [account, provider] = await Promise.all([this.getAccount(), this.getProvider()]); const isChainsStale = await this._isChainsStale(); // If an account does not exist on the session, then the connector is unauthorized. if (!account) { return false; } // If the chains are stale on the session, then the connector is unauthorized. if (isChainsStale && provider.session) { try { await provider.disconnect(); } catch {} // eslint-disable-line no-empty return false; } return true; } catch { return false; } } async switchChain(chainId) { const chain = this.chains.find(chain_ => chain_.chainId === chainId); if (!chain) { throw new errors.SwitchChainError(`Chain with ID: ${chainId}, not found on connector.`); } try { const provider = await this.getProvider(); const namespaceChains = this._getNamespaceChainsIds(); const namespaceMethods = this._getNamespaceMethods(); const isChainApproved = namespaceChains.includes(chainId); if (!isChainApproved && namespaceMethods.includes(ADD_ETH_CHAIN_METHOD)) { const firstExplorer = chain.explorers && chain.explorers[0]; const blockExplorerUrls = firstExplorer ? { blockExplorerUrls: [firstExplorer.url] } : {}; await provider.request({ method: ADD_ETH_CHAIN_METHOD, params: [{ chainId: ethers.utils.hexValue(chain.chainId), chainName: chain.name, nativeCurrency: chain.nativeCurrency, rpcUrls: url.getValidPublicRPCUrl(chain), // no clientId on purpose ...blockExplorerUrls }] }); const requestedChains = await this._getRequestedChainsIds(); requestedChains.push(chainId); await this._setRequestedChainsIds(requestedChains); } await provider.request({ method: "wallet_switchEthereumChain", params: [{ chainId: ethers.utils.hexValue(chainId) }] }); return chain; } catch (error) { const message = typeof error === "string" ? error : error?.message; if (/user rejected request/i.test(message)) { throw new errors.UserRejectedRequestError(error); } throw new errors.SwitchChainError(error); } } async _createProvider() { if (!this._initProviderPromise && typeof window !== "undefined") { this._initProviderPromise = this.initProvider(); } return this._initProviderPromise; } async initProvider() { const { default: EthereumProvider, OPTIONAL_EVENTS, OPTIONAL_METHODS } = await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('@walletconnect/ethereum-provider')); }); const [defaultChain, ...optionalChains] = this.filteredChains.map(_ref2 => { let { chainId } = _ref2; return chainId; }); if (defaultChain) { // EthereumProvider populates & deduplicates required methods and events internally this._provider = await EthereumProvider.init({ showQrModal: this.showWalletConnectModal, projectId: this.options.projectId, methods: ["eth_sendTransaction", "personal_sign", "eth_signTypedData_v4"], optionalMethods: OPTIONAL_METHODS, optionalEvents: OPTIONAL_EVENTS, chains: [defaultChain], optionalChains: optionalChains, metadata: { name: this.options.dappMetadata.name, description: this.options.dappMetadata.description || "", url: this.options.dappMetadata.url, icons: [this.options.dappMetadata.logoUrl || ""] }, rpcMap: Object.fromEntries(this.filteredChains.map(chain => [chain.chainId, chain.rpc[0] || "" // TODO: handle chain.rpc being empty array ])), qrModalOptions: this.options.qrModalOptions }); } } /** * Checks if the target chains match the chains that were * initially requested by the connector for the WalletConnect session. * If there is a mismatch, this means that the chains on the connector * are considered stale, and need to be revalidated at a later point (via * connection). * * There may be a scenario where a dapp adds a chain to the * connector later on, however, this chain will not have been approved or rejected * by the wallet. In this case, the chain is considered stale. * * There are exceptions however: * - If the wallet supports dynamic chain addition via `eth_addEthereumChain`, * then the chain is not considered stale. * - If the `isNewChainsStale` flag is falsy on the connector, then the chain is * not considered stale. * * For the above cases, chain validation occurs dynamically when the user * attempts to switch chain. * * Also check that dapp supports at least 1 chain from previously approved session. */ async _isChainsStale() { const namespaceMethods = this._getNamespaceMethods(); if (namespaceMethods.includes(ADD_ETH_CHAIN_METHOD)) { return false; } if (!this.options.isNewChainsStale) { return false; } const requestedChains = await this._getRequestedChainsIds(); const connectorChains = this.filteredChains.map(_ref3 => { let { chainId } = _ref3; return chainId; }); const namespaceChains = this._getNamespaceChainsIds(); if (namespaceChains.length && !namespaceChains.some(id => connectorChains.includes(id))) { return false; } return !connectorChains.every(id => requestedChains.includes(id)); } async setupListeners() { if (!this._provider) { return; } this._removeListeners(); this._provider.on("accountsChanged", this.onAccountsChanged); this._provider.on("chainChanged", this.onChainChanged); this._provider.on("disconnect", this.onDisconnect); this._provider.on("session_delete", this.onDisconnect); this._provider.on("display_uri", this.onDisplayUri); this._provider.on("connect", this.onConnect); } _removeListeners() { if (!this._provider) { return; } this._provider.removeListener("accountsChanged", this.onAccountsChanged); this._provider.removeListener("chainChanged", this.onChainChanged); this._provider.removeListener("disconnect", this.onDisconnect); this._provider.removeListener("session_delete", this.onDisconnect); this._provider.removeListener("display_uri", this.onDisplayUri); this._provider.removeListener("connect", this.onConnect); } async _setRequestedChainsIds(chains) { await this._storage.setItem(REQUESTED_CHAINS_KEY, JSON.stringify(chains)); } async _getRequestedChainsIds() { const data = await this._storage.getItem(REQUESTED_CHAINS_KEY); return data ? JSON.parse(data) : []; } _getNamespaceChainsIds() { if (!this._provider) { return []; } const chainIds = this._provider.session?.namespaces[NAMESPACE]?.chains?.map(chain => parseInt(chain.split(":")[1] || "")); return chainIds ?? []; } _getNamespaceMethods() { if (!this._provider) { return []; } const methods = this._provider.session?.namespaces[NAMESPACE]?.methods; return methods ?? []; } } exports.WalletConnectConnector = WalletConnectConnector;