UNPKG

@wagmi/connectors

Version:

Collection of connectors for Wagmi

325 lines (302 loc) 16 kB
import { ChainNotConfiguredError, type Connector, createConnector, } from '@wagmi/core' import type { ExactPartial } from '@wagmi/core/internal' import type { Porto, RpcSchema } from 'porto' import { type Address, getAddress, numberToHex, type ProviderConnectInfo, type RpcError, SwitchChainError, UserRejectedRequestError, withRetry, } from 'viem' export type PortoParameters = ExactPartial<Porto.Config> export function porto(parameters: PortoParameters = {}) { type Provider = ReturnType<typeof Porto.create>['provider'] type Properties = { connect<withCapabilities extends boolean = false>(parameters?: { chainId?: number | undefined capabilities?: | (RpcSchema.wallet_connect.Capabilities & { force?: boolean | undefined }) | undefined isReconnecting?: boolean | undefined withCapabilities?: withCapabilities | boolean | undefined }): Promise<{ accounts: withCapabilities extends true ? readonly { address: Address capabilities: RpcSchema.wallet_connect.ResponseCapabilities }[] : readonly Address[] chainId: number }> getPortoInstance(): Promise<Porto.Porto> onConnect(connectInfo: ProviderConnectInfo): void } return createConnector<Provider, Properties>((wagmiConfig) => { const chains = wagmiConfig.chains ?? parameters.chains ?? [] const transports = (() => { if (wagmiConfig.transports) return wagmiConfig.transports return parameters.transports })() let porto_promise: Promise<any> | undefined let accountsChanged: Connector['onAccountsChanged'] | undefined let chainChanged: Connector['onChainChanged'] | undefined let connect: Connector['onConnect'] | undefined let disconnect: Connector['onDisconnect'] | undefined return { async connect({ chainId = chains[0].id, ...rest } = {}) { const isReconnecting = ('isReconnecting' in rest && rest.isReconnecting) || false const withCapabilities = ('withCapabilities' in rest && rest.withCapabilities) || false let accounts: readonly (Address | { address: Address })[] = [] let currentChainId: number | undefined if (isReconnecting) { ;[accounts, currentChainId] = await Promise.all([ this.getAccounts().catch(() => []), this.getChainId().catch(() => undefined), ]) if (chainId && currentChainId !== chainId) { const chain = await this.switchChain!({ chainId }).catch( (error) => { if (error.code === UserRejectedRequestError.code) throw error return { id: currentChainId } }, ) currentChainId = chain?.id ?? currentChainId } } const provider = (await this.getProvider()) as Provider try { if (!accounts?.length && !isReconnecting) { const { RpcSchema } = await (() => { // safe webpack optional peer dependency dynamic import try { return import('porto') } catch { throw new Error('dependency "porto" not found') } })() const { z } = await (() => { // safe webpack optional peer dependency dynamic import try { return import('porto/internal') } catch { throw new Error('dependency "porto/internal" not found') } })() const res = await provider.request({ method: 'wallet_connect', params: [ { ...('capabilities' in rest ? { capabilities: z.encode( RpcSchema.wallet_connect.Capabilities, rest.capabilities ?? {}, ), } : {}), chainIds: [ numberToHex(chainId), ...chains .filter((x) => x.id !== chainId) .map((x) => numberToHex(x.id)), ], }, ], }) accounts = res.accounts currentChainId = Number(res.chainIds[0]) } if (!currentChainId) throw new ChainNotConfiguredError() // Manage EIP-1193 event listeners // https://eips.ethereum.org/EIPS/eip-1193#events if (connect) { provider.removeListener('connect', connect) connect = undefined } if (!accountsChanged) { accountsChanged = this.onAccountsChanged.bind(this) // Porto Provider uses Ox, which uses `readonly Address.Address[]` for `accountsChanged`, // while Connector `accountsChanged` is `string[]` provider.on('accountsChanged', accountsChanged as never) } if (!chainChanged) { chainChanged = this.onChainChanged.bind(this) provider.on('chainChanged', chainChanged) } if (!disconnect) { disconnect = this.onDisconnect.bind(this) provider.on('disconnect', disconnect) } return { accounts: accounts.map((account) => { if (typeof account === 'object') return withCapabilities ? account : account.address return withCapabilities ? { address: account, capabilities: {} } : account }) as never, chainId: currentChainId, } } catch (err) { const error = err as RpcError if (error.code === UserRejectedRequestError.code) throw new UserRejectedRequestError(error) throw error } }, async disconnect() { const provider = await this.getProvider() 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) } await provider.request({ method: 'wallet_disconnect' }) }, async getAccounts() { const provider = await this.getProvider() const accounts = await provider.request({ method: 'eth_accounts', }) return accounts.map((x) => getAddress(x)) }, async getChainId() { const provider = await this.getProvider() const hexChainId = await provider.request({ method: 'eth_chainId', }) return Number(hexChainId) }, async getPortoInstance() { porto_promise ??= (async () => { const { Porto } = await (() => { // safe webpack optional peer dependency dynamic import try { return import('porto') } catch { throw new Error('dependency "porto" not found') } })() // @ts-ignore return Porto.create({ ...parameters, announceProvider: false, // @ts-ignore chains, // @ts-ignore transports, }) })() return await porto_promise }, async getProvider() { return (await this.getPortoInstance()).provider }, icon: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDIyIiBoZWlnaHQ9IjQyMiIgdmlld0JveD0iMCAwIDQyMiA0MjIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSI0MjIiIGhlaWdodD0iNDIyIiBmaWxsPSJibGFjayIvPgo8ZyBjbGlwLXBhdGg9InVybCgjY2xpcDBfMV8xNSkiPgo8cGF0aCBkPSJNODEgMjg2LjM2NkM4MSAyODAuODkzIDg1LjQ1MDUgMjc2LjQ1NSA5MC45NDA0IDI3Ni40NTVIMzI5LjUxMUMzMzUuMDAxIDI3Ni40NTUgMzM5LjQ1MiAyODAuODkzIDMzOS40NTIgMjg2LjM2NlYzMDYuMTg4QzMzOS40NTIgMzExLjY2MiAzMzUuMDAxIDMxNi4wOTkgMzI5LjUxMSAzMTYuMDk5SDkwLjk0MDRDODUuNDUwNSAzMTYuMDk5IDgxIDMxMS42NjIgODEgMzA2LjE4OFYyODYuMzY2WiIgZmlsbD0id2hpdGUiIGZpbGwtb3BhY2l0eT0iMC41Ii8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNOTAuOTQwNCAyMzQuODI4Qzg1LjQ1MDUgMjM0LjgyOCA4MSAyMzkuMjY2IDgxIDI0NC43MzlWMjcxLjUzMUM4My44NDMyIDI2OS42MzMgODcuMjYyMiAyNjguNTI2IDkwLjk0MDQgMjY4LjUyNkgzMjkuNTExQzMzMy4xODggMjY4LjUyNiAzMzYuNjA4IDI2OS42MzMgMzM5LjQ1MiAyNzEuNTMxVjI0NC43MzlDMzM5LjQ1MiAyMzkuMjY2IDMzNS4wMDEgMjM0LjgyOCAzMjkuNTExIDIzNC44MjhIOTAuOTQwNFpNMzM5LjQ1MiAyODYuMzY2QzMzOS40NTIgMjgwLjg5MyAzMzUuMDAxIDI3Ni40NTUgMzI5LjUxMSAyNzYuNDU1SDkwLjk0MDRDODUuNDUwNSAyNzYuNDU1IDgxIDI4MC44OTMgODEgMjg2LjM2NlYzMDYuMTlDODEgMzExLjY2NCA4NS40NTA1IDMxNi4xMDEgOTAuOTQwNCAzMTYuMTAxSDMyOS41MTFDMzM1LjAwMSAzMTYuMTAxIDMzOS40NTIgMzExLjY2NCAzMzkuNDUyIDMwNi4xOVYyODYuMzY2WiIgZmlsbD0id2hpdGUiIGZpbGwtb3BhY2l0eT0iMC41Ii8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNOTAuOTQwNCAxOTMuMjAxQzg1LjQ1MDUgMTkzLjIwMSA4MSAxOTcuNjM4IDgxIDIwMy4xMTJWMjI5LjkwM0M4My44NDMyIDIyOC4wMDYgODcuMjYyMiAyMjYuODk5IDkwLjk0MDQgMjI2Ljg5OUgzMjkuNTExQzMzMy4xODggMjI2Ljg5OSAzMzYuNjA4IDIyOC4wMDYgMzM5LjQ1MiAyMjkuOTAzVjIwMy4xMTJDMzM5LjQ1MiAxOTcuNjM4IDMzNS4wMDEgMTkzLjIwMSAzMjkuNTExIDE5My4yMDFIOTAuOTQwNFpNMzM5LjQ1MiAyNDQuNzM5QzMzOS40NTIgMjM5LjI2NSAzMzUuMDAxIDIzNC44MjggMzI5LjUxMSAyMzQuODI4SDkwLjk0MDRDODUuNDUwNSAyMzQuODI4IDgxIDIzOS4yNjUgODEgMjQ0LjczOVYyNzEuNTNDODEuMjE3NSAyNzEuMzg1IDgxLjQzODMgMjcxLjI0NSA4MS42NjI0IDI3MS4xMDlDODMuODMyNSAyNjkuNzk0IDg2LjMwNTQgMjY4LjkyNyA4OC45NTIzIDI2OC42MzVDODkuNjA1MSAyNjguNTYzIDkwLjI2ODQgMjY4LjUyNiA5MC45NDA0IDI2OC41MjZIMzI5LjUxMUMzMzAuMTgzIDI2OC41MjYgMzMwLjg0NiAyNjguNTYzIDMzMS40OTggMjY4LjYzNUMzMzQuNDE5IDI2OC45NTcgMzM3LjEyOCAyNjkuOTggMzM5LjQ1MiAyNzEuNTNWMjQ0LjczOVpNMzM5LjQ1MiAyODYuMzY2QzMzOS40NTIgMjgxLjAyMSAzMzUuMjA2IDI3Ni42NjMgMzI5Ljg5MyAyNzYuNDYyQzMyOS43NjcgMjc2LjQ1NyAzMjkuNjQgMjc2LjQ1NSAzMjkuNTExIDI3Ni40NTVIOTAuOTQwNEM4NS40NTA1IDI3Ni40NTUgODEgMjgwLjg5MyA4MSAyODYuMzY2VjMwNi4xODhDODEgMzExLjY2MiA4NS40NTA1IDMxNi4xMDEgOTAuOTQwNCAzMTYuMTAxSDMyOS41MTFDMzM1LjAwMSAzMTYuMTAxIDMzOS40NTIgMzExLjY2MiAzMzkuNDUyIDMwNi4xODhWMjg2LjM2NloiIGZpbGw9IndoaXRlIiBmaWxsLW9wYWNpdHk9IjAuNSIvPgo8cGF0aCBvcGFjaXR5PSIwLjMiIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNOTguMDE0NiAxMDRDODguNjE3NyAxMDQgODEgMTExLjU5NSA4MSAxMjAuOTY1VjE4OC4yNzZDODMuODQzMiAxODYuMzc5IDg3LjI2MjIgMTg1LjI3MiA5MC45NDA0IDE4NS4yNzJIMzI5LjUxMUMzMzMuMTg4IDE4NS4yNzIgMzM2LjYwOCAxODYuMzc5IDMzOS40NTIgMTg4LjI3NlYxMjAuOTY1QzMzOS40NTIgMTExLjU5NSAzMzEuODMzIDEwNCAzMjIuNDM3IDEwNEg5OC4wMTQ2Wk0zMzkuNDUyIDIwMy4xMTJDMzM5LjQ1MiAxOTcuNjM4IDMzNS4wMDEgMTkzLjIwMSAzMjkuNTExIDE5My4yMDFIOTAuOTQwNEM4NS40NTA1IDE5My4yMDEgODEgMTk3LjYzOCA4MSAyMDMuMTEyVjIyOS45MDNDODEuMjE3NSAyMjkuNzU4IDgxLjQzODMgMjI5LjYxOCA4MS42NjI0IDIyOS40ODJDODMuODMyNSAyMjguMTY3IDg2LjMwNTQgMjI3LjMgODguOTUyMyAyMjcuMDA4Qzg5LjYwNTEgMjI2LjkzNiA5MC4yNjg0IDIyNi44OTkgOTAuOTQwNCAyMjYuODk5SDMyOS41MTFDMzMwLjE4MyAyMjYuODk5IDMzMC44NDYgMjI2LjkzNiAzMzEuNDk4IDIyNy4wMDhDMzM0LjQxOSAyMjcuMzMgMzM3LjEyOCAyMjguMzUyIDMzOS40NTIgMjI5LjkwM1YyMDMuMTEyWk0zMzkuNDUyIDI0NC43MzlDMzM5LjQ1MiAyMzkuMzkzIDMzNS4yMDYgMjM1LjAzNiAzMjkuODkzIDIzNC44MzVDMzI5Ljc2NyAyMzQuODMgMzI5LjY0IDIzNC44MjggMzI5LjUxMSAyMzQuODI4SDkwLjk0MDRDODUuNDUwNSAyMzQuODI4IDgxIDIzOS4yNjUgODEgMjQ0LjczOVYyNzEuNTNMODEuMDcwNyAyNzEuNDgzQzgxLjI2NTMgMjcxLjM1NSA4MS40NjI1IDI3MS4yMyA4MS42NjI0IDI3MS4xMDlDODEuOTA4MyAyNzAuOTYgODIuMTU4MSAyNzAuODE3IDgyLjQxMTcgMjcwLjY3OUM4NC4zOTUzIDI2OS42MDUgODYuNjA1NCAyNjguODk0IDg4Ljk1MjMgMjY4LjYzNUM4OS4wMDUyIDI2OC42MjkgODkuMDU4IDI2OC42MjQgODkuMTExIDI2OC42MThDODkuNzEyNSAyNjguNTU3IDkwLjMyMjggMjY4LjUyNiA5MC45NDA0IDI2OC41MjZIMzI5LjUxMUMzMjkuNzM4IDI2OC41MjYgMzI5Ljk2NSAyNjguNTMgMzMwLjE5MiAyNjguNTM5QzMzMC42MzEgMjY4LjU1NSAzMzEuMDY3IDI2OC41ODcgMzMxLjQ5OCAyNjguNjM1QzMzNC40MTkgMjY4Ljk1NyAzMzcuMTI4IDI2OS45OCAzMzkuNDUyIDI3MS41M1YyNDQuNzM5Wk0zMzkuNDUyIDI4Ni4zNjZDMzM5LjQ1MiAyODEuMDIxIDMzNS4yMDYgMjc2LjY2MyAzMjkuODkzIDI3Ni40NjJMMzI5Ljg2NSAyNzYuNDYxQzMyOS43NDggMjc2LjQ1NyAzMjkuNjI5IDI3Ni40NTUgMzI5LjUxMSAyNzYuNDU1SDkwLjk0MDRDODUuNDUwNSAyNzYuNDU1IDgxIDI4MC44OTMgODEgMjg2LjM2NlYzMDYuMTg4QzgxIDMxMS42NjIgODUuNDUwNSAzMTYuMTAxIDkwLjk0MDQgMzE2LjEwMUgzMjkuNTExQzMzNS4wMDEgMzE2LjEwMSAzMzkuNDUyIDMxMS42NjIgMzM5LjQ1MiAzMDYuMTg4VjI4Ni4zNjZaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMjY5Ljg2OCAxMzEuNzUyQzI2OS44NjggMTI2LjI3OCAyNzQuMzE4IDEyMS44NCAyNzkuODA4IDEyMS44NEgzMTEuNjE4QzMxNy4xMDggMTIxLjg0IDMyMS41NTggMTI2LjI3OCAzMjEuNTU4IDEzMS43NTJWMTYxLjQ4NUMzMjEuNTU4IDE2Ni45NTkgMzE3LjEwOCAxNzEuMzk2IDMxMS42MTggMTcxLjM5NkgyNzkuODA4QzI3NC4zMTggMTcxLjM5NiAyNjkuODY4IDE2Ni45NTkgMjY5Ljg2OCAxNjEuNDg1VjEzMS43NTJaIiBmaWxsPSJ3aGl0ZSIvPgo8L2c+CjxkZWZzPgo8Y2xpcFBhdGggaWQ9ImNsaXAwXzFfMTUiPgo8cmVjdCB3aWR0aD0iMjU5IiBoZWlnaHQ9IjIxMyIgZmlsbD0id2hpdGUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDgxIDEwNCkiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K', id: 'xyz.ithaca.porto', async isAuthorized() { try { // Use retry strategy as some injected wallets (e.g. MetaMask) fail to // immediately resolve JSON-RPC requests on page load. const accounts = await withRetry(() => this.getAccounts()) return !!accounts.length } catch { return false } }, name: 'Porto', async onAccountsChanged(accounts) { wagmiConfig.emitter.emit('change', { accounts: accounts.map((x) => getAddress(x)), }) }, onChainChanged(chain) { const chainId = Number(chain) wagmiConfig.emitter.emit('change', { chainId }) }, async onConnect(connectInfo) { const accounts = await this.getAccounts() if (accounts.length === 0) return const chainId = Number(connectInfo.chainId) wagmiConfig.emitter.emit('connect', { accounts, chainId }) // Manage EIP-1193 event listeners const provider = await this.getProvider() if (provider) { if (connect) { provider.removeListener('connect', connect) connect = undefined } if (!accountsChanged) { accountsChanged = this.onAccountsChanged.bind(this) // Porto Provider uses Ox, which uses `readonly Address.Address[]` for `accountsChanged`, // while Connector `accountsChanged` is `string[]` provider.on('accountsChanged', accountsChanged as never) } if (!chainChanged) { chainChanged = this.onChainChanged.bind(this) provider.on('chainChanged', chainChanged) } if (!disconnect) { disconnect = this.onDisconnect.bind(this) provider.on('disconnect', disconnect) } } }, async onDisconnect(_error) { const provider = await this.getProvider() wagmiConfig.emitter.emit('disconnect') // Manage EIP-1193 event listeners if (provider) { 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) } } }, async setup() { if (!connect) { const provider = await this.getProvider() connect = this.onConnect.bind(this) provider.on('connect', connect) } }, async switchChain({ chainId }) { const chain = chains.find((x) => x.id === chainId) if (!chain) throw new SwitchChainError(new ChainNotConfiguredError()) const provider = await this.getProvider() await provider.request({ method: 'wallet_switchEthereumChain', params: [{ chainId: numberToHex(chainId) }], }) return chain }, type: 'injected', } }) }