UNPKG

@wagmi/connectors

Version:

Collection of connectors for Wagmi

351 lines 15.4 kB
import { ChainNotConfiguredError, ProviderNotFoundError, createConnector, extractRpcUrls, } from '@wagmi/core'; import { SwitchChainError, UserRejectedRequestError, getAddress, numberToHex, } from 'viem'; walletConnect.type = 'walletConnect'; export function walletConnect(parameters) { const isNewChainsStale = parameters.isNewChainsStale ?? true; let provider_; let providerPromise; const NAMESPACE = 'eip155'; let accountsChanged; let chainChanged; let connect; let displayUri; let sessionDelete; let disconnect; return createConnector((config) => ({ id: 'walletConnect', name: 'WalletConnect', type: walletConnect.type, async setup() { const provider = await this.getProvider().catch(() => null); if (!provider) return; if (!connect) { connect = this.onConnect.bind(this); provider.on('connect', connect); } if (!sessionDelete) { sessionDelete = this.onSessionDelete.bind(this); provider.on('session_delete', sessionDelete); } }, async connect({ chainId, ...rest } = {}) { try { const provider = await this.getProvider(); if (!provider) throw new ProviderNotFoundError(); if (!displayUri) { displayUri = this.onDisplayUri; provider.on('display_uri', displayUri); } let targetChainId = chainId; if (!targetChainId) { const state = (await config.storage?.getItem('state')) ?? {}; const isChainSupported = config.chains.some((x) => x.id === state.chainId); if (isChainSupported) targetChainId = state.chainId; else targetChainId = config.chains[0]?.id; } if (!targetChainId) throw new Error('No chains found on connector.'); const isChainsStale = await this.isChainsStale(); // If there is an active session with stale chains, disconnect current session. if (provider.session && isChainsStale) await provider.disconnect(); // If there isn't an active session or chains are stale, connect. if (!provider.session || isChainsStale) { const optionalChains = config.chains .filter((chain) => chain.id !== targetChainId) .map((optionalChain) => optionalChain.id); await provider.connect({ optionalChains: [targetChainId, ...optionalChains], ...('pairingTopic' in rest ? { pairingTopic: rest.pairingTopic } : {}), }); this.setRequestedChainsIds(config.chains.map((x) => x.id)); } // If session exists and chains are authorized, enable provider for required chain const accounts = (await provider.enable()).map((x) => getAddress(x)); const currentChainId = await this.getChainId(); if (displayUri) { provider.removeListener('display_uri', displayUri); displayUri = undefined; } if (connect) { provider.removeListener('connect', connect); connect = undefined; } if (!accountsChanged) { accountsChanged = this.onAccountsChanged.bind(this); provider.on('accountsChanged', accountsChanged); } if (!chainChanged) { chainChanged = this.onChainChanged.bind(this); provider.on('chainChanged', chainChanged); } if (!disconnect) { disconnect = this.onDisconnect.bind(this); provider.on('disconnect', disconnect); } if (!sessionDelete) { sessionDelete = this.onSessionDelete.bind(this); provider.on('session_delete', sessionDelete); } return { accounts, chainId: currentChainId }; } catch (error) { if (/(user rejected|connection request reset)/i.test(error?.message)) { throw new UserRejectedRequestError(error); } throw error; } }, async disconnect() { const provider = await this.getProvider(); try { await provider?.disconnect(); } catch (error) { if (!/No matching key/i.test(error.message)) throw error; } finally { if (chainChanged) { provider?.removeListener('chainChanged', chainChanged); chainChanged = undefined; } if (disconnect) { provider?.removeListener('disconnect', disconnect); disconnect = undefined; } if (!connect) { connect = this.onConnect.bind(this); provider?.on('connect', connect); } if (accountsChanged) { provider?.removeListener('accountsChanged', accountsChanged); accountsChanged = undefined; } if (sessionDelete) { provider?.removeListener('session_delete', sessionDelete); sessionDelete = undefined; } this.setRequestedChainsIds([]); } }, async getAccounts() { const provider = await this.getProvider(); return provider.accounts.map((x) => getAddress(x)); }, async getProvider({ chainId } = {}) { async function initProvider() { const optionalChains = config.chains.map((x) => x.id); if (!optionalChains.length) return; const { EthereumProvider } = await import('@walletconnect/ethereum-provider'); return await EthereumProvider.init({ ...parameters, disableProviderPing: true, optionalChains, projectId: parameters.projectId, rpcMap: Object.fromEntries(config.chains.map((chain) => { const [url] = extractRpcUrls({ chain, transports: config.transports, }); return [chain.id, url]; })), showQrModal: parameters.showQrModal ?? true, }); } if (!provider_) { if (!providerPromise) providerPromise = initProvider(); provider_ = await providerPromise; provider_?.events.setMaxListeners(Number.POSITIVE_INFINITY); } if (chainId) await this.switchChain?.({ chainId }); return provider_; }, async getChainId() { const provider = await this.getProvider(); return provider.chainId; }, async isAuthorized() { try { const [accounts, provider] = await Promise.all([ this.getAccounts(), this.getProvider(), ]); // If an account does not exist on the session, then the connector is unauthorized. if (!accounts.length) return false; // If the chains are stale on the session, then the connector is unauthorized. const isChainsStale = await this.isChainsStale(); if (isChainsStale && provider.session) { await provider.disconnect().catch(() => { }); return false; } return true; } catch { return false; } }, async switchChain({ addEthereumChainParameter, chainId }) { const provider = await this.getProvider(); if (!provider) throw new ProviderNotFoundError(); const chain = config.chains.find((x) => x.id === chainId); if (!chain) throw new SwitchChainError(new ChainNotConfiguredError()); try { await Promise.all([ new Promise((resolve) => { const listener = ({ chainId: currentChainId, }) => { if (currentChainId === chainId) { config.emitter.off('change', listener); resolve(); } }; config.emitter.on('change', listener); }), provider.request({ method: 'wallet_switchEthereumChain', params: [{ chainId: numberToHex(chainId) }], }), ]); const requestedChains = await this.getRequestedChainsIds(); this.setRequestedChainsIds([...requestedChains, chainId]); return chain; } catch (err) { const error = err; if (/(user rejected)/i.test(error.message)) throw new UserRejectedRequestError(error); // Indicates chain is not added to provider try { let blockExplorerUrls; if (addEthereumChainParameter?.blockExplorerUrls) blockExplorerUrls = addEthereumChainParameter.blockExplorerUrls; else blockExplorerUrls = chain.blockExplorers?.default.url ? [chain.blockExplorers?.default.url] : []; let rpcUrls; if (addEthereumChainParameter?.rpcUrls?.length) rpcUrls = addEthereumChainParameter.rpcUrls; else rpcUrls = [...chain.rpcUrls.default.http]; const addEthereumChain = { blockExplorerUrls, chainId: numberToHex(chainId), chainName: addEthereumChainParameter?.chainName ?? chain.name, iconUrls: addEthereumChainParameter?.iconUrls, nativeCurrency: addEthereumChainParameter?.nativeCurrency ?? chain.nativeCurrency, rpcUrls, }; await provider.request({ method: 'wallet_addEthereumChain', params: [addEthereumChain], }); const requestedChains = await this.getRequestedChainsIds(); this.setRequestedChainsIds([...requestedChains, chainId]); return chain; } catch (error) { throw new UserRejectedRequestError(error); } } }, onAccountsChanged(accounts) { if (accounts.length === 0) this.onDisconnect(); else config.emitter.emit('change', { accounts: accounts.map((x) => getAddress(x)), }); }, onChainChanged(chain) { const chainId = Number(chain); config.emitter.emit('change', { chainId }); }, async onConnect(connectInfo) { const chainId = Number(connectInfo.chainId); const accounts = await this.getAccounts(); config.emitter.emit('connect', { accounts, chainId }); }, async onDisconnect(_error) { this.setRequestedChainsIds([]); config.emitter.emit('disconnect'); const provider = await this.getProvider(); if (accountsChanged) { provider.removeListener('accountsChanged', accountsChanged); accountsChanged = undefined; } if (chainChanged) { provider.removeListener('chainChanged', chainChanged); chainChanged = undefined; } if (disconnect) { provider.removeListener('disconnect', disconnect); disconnect = undefined; } if (sessionDelete) { provider.removeListener('session_delete', sessionDelete); sessionDelete = undefined; } if (!connect) { connect = this.onConnect.bind(this); provider.on('connect', connect); } }, onDisplayUri(uri) { config.emitter.emit('message', { type: 'display_uri', data: uri }); }, onSessionDelete() { this.onDisconnect(); }, getNamespaceChainsIds() { if (!provider_) return []; const chainIds = provider_.session?.namespaces[NAMESPACE]?.accounts?.map((account) => Number.parseInt(account.split(':')[1] || '')); return chainIds ?? []; }, async getRequestedChainsIds() { return ((await config.storage?.getItem(this.requestedChainsStorageKey)) ?? []); }, /** * 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. */ async isChainsStale() { if (!isNewChainsStale) return false; const connectorChains = config.chains.map((x) => x.id); const namespaceChains = this.getNamespaceChainsIds(); if (namespaceChains.length && !namespaceChains.some((id) => connectorChains.includes(id))) return false; const requestedChains = await this.getRequestedChainsIds(); return !connectorChains.every((id) => requestedChains.includes(id)); }, async setRequestedChainsIds(chains) { await config.storage?.setItem(this.requestedChainsStorageKey, chains); }, get requestedChainsStorageKey() { return `${this.id}.requestedChains`; }, })); } //# sourceMappingURL=walletConnect.js.map