UNPKG

saepenatus

Version:

Web3-Onboard makes it simple to connect Ethereum hardware and software wallets to your dapp. Features standardised spec compliant web3 providers for all supported wallets, framework agnostic modern javascript UI with code splitting, CSS customization, mul

280 lines (241 loc) 9.51 kB
import { Chain, WalletInit, GetInterfaceHelpers, EIP1193Provider, ProviderAccounts } from '@web3-onboard/common' import type { EthereumProvider } from '@ledgerhq/connect-kit-loader' import type { StaticJsonRpcProvider as StaticJsonRpcProviderType } from '@ethersproject/providers' import WalletConnect from '@walletconnect/client' const isHexString = (value: string | number) => { if (typeof value !== 'string' || !value.match(/^0x[0-9A-Fa-f]*$/)) { return false } return true } interface LedgerOptions { chainId?: number bridge?: string infuraId?: string rpc?: { [chainId: number]: string } } function ledger(options?: LedgerOptions): WalletInit { return () => { return { label: 'Ledger', getIcon: async () => (await import('./icon.js')).default, getInterface: async ({ chains, EventEmitter }: GetInterfaceHelpers) => { const { loadConnectKit, SupportedProviders, SupportedProviderImplementations } = await import('@ledgerhq/connect-kit-loader') const connectKit = await loadConnectKit() connectKit.enableDebugLogs() const checkSupportResult = connectKit.checkSupport({ providerType: SupportedProviders.Ethereum, chainId: options?.chainId, infuraId: options?.infuraId, rpc: options?.rpc }) // get the Ledger provider instance, it can be either Ledger Connect // or WalletConnect const instance = (await connectKit.getProvider()) as EthereumProvider // return the Ledger Connect provider if ( checkSupportResult.providerImplementation === SupportedProviderImplementations.LedgerConnect ) { return { provider: instance as EIP1193Provider } } // fallback to WalletConnect on unsupported platforms const { StaticJsonRpcProvider } = await import( '@ethersproject/providers' ) const { ProviderRpcError, ProviderRpcErrorCode } = await import( '@web3-onboard/common' ) const { default: WalletConnect } = await import('@walletconnect/client') const { Subject, fromEvent } = await import('rxjs') const { takeUntil, take } = await import('rxjs/operators') const connector = instance.connector as WalletConnect const emitter = new EventEmitter() class EthProvider { public request: EIP1193Provider['request'] public connector: InstanceType<typeof WalletConnect> public chains: Chain[] public disconnect: EIP1193Provider['disconnect'] public emit: typeof EventEmitter['emit'] public on: typeof EventEmitter['on'] public removeListener: typeof EventEmitter['removeListener'] private disconnected$: InstanceType<typeof Subject> private providers: Record<string, StaticJsonRpcProviderType> constructor({ connector, chains }: { connector: InstanceType<typeof WalletConnect> chains: Chain[] }) { this.emit = emitter.emit.bind(emitter) this.on = emitter.on.bind(emitter) this.removeListener = emitter.removeListener.bind(emitter) this.connector = connector this.chains = chains this.disconnected$ = new Subject() this.providers = {} // listen for session updates fromEvent(this.connector, 'session_update', (error, payload) => { if (error) { throw error } return payload }) .pipe(takeUntil(this.disconnected$)) .subscribe({ next: ({ params }) => { const [{ accounts, chainId }] = params this.emit('accountsChanged', accounts) const hexChainId = isHexString(chainId) ? chainId : `0x${chainId.toString(16)}` this.emit('chainChanged', hexChainId) }, error: console.warn }) // listen for disconnect event fromEvent(this.connector, 'disconnect', (error, payload) => { if (error) { throw error } return payload }) .pipe(takeUntil(this.disconnected$)) .subscribe({ next: () => { this.emit('accountsChanged', []) this.disconnected$.next(true) typeof localStorage !== 'undefined' && localStorage.removeItem('walletconnect') }, error: console.warn }) this.disconnect = () => this.connector.killSession() this.request = async ({ method, params }) => { if (method === 'eth_chainId') { return isHexString(this.connector.chainId) ? this.connector.chainId : `0x${this.connector.chainId.toString(16)}` } if (method === 'eth_requestAccounts') { return new Promise<ProviderAccounts>((resolve, reject) => { // Check if connection is already established if (!this.connector.connected) { resolve((instance as any).request({ method })) } else { const { accounts, chainId } = this.connector.session const hexChainId = isHexString(chainId) ? chainId : `0x${chainId.toString(16)}` this.emit('chainChanged', hexChainId) return resolve(accounts) } // Subscribe to connection events fromEvent(this.connector, 'connect', (error, payload) => { if (error) { throw error } return payload }) .pipe(take(1)) .subscribe({ next: ({ params }) => { const [{ accounts, chainId }] = params this.emit('accountsChanged', accounts) const hexChainId = isHexString(chainId) ? chainId : `0x${chainId.toString(16)}` this.emit('chainChanged', hexChainId) resolve(accounts) }, error: reject }) }) } if (method === 'eth_selectAccounts') { throw new ProviderRpcError({ code: ProviderRpcErrorCode.UNSUPPORTED_METHOD, message: `The Provider does not support the requested method: ${method}` }) } if (method == 'wallet_switchEthereumChain') { throw new ProviderRpcError({ code: ProviderRpcErrorCode.UNSUPPORTED_METHOD, message: `The Provider does not support the requested method: ${method}` }) } // @ts-ignore if (method === 'eth_sendTransaction') { // @ts-ignore return this.connector.sendTransaction(params[0]) } // @ts-ignore if (method === 'eth_signTransaction') { // @ts-ignore return this.connector.signTransaction(params[0]) } // @ts-ignore if (method === 'personal_sign') { // @ts-ignore return this.connector.signPersonalMessage(params) } // @ts-ignore if (method === 'eth_sign') { // @ts-ignore return this.connector.signMessage(params) } // @ts-ignore if (method.includes('eth_signTypedData')) { // @ts-ignore return this.connector.signTypedData(params) } if (method === 'eth_accounts') { return this.connector.sendCustomRequest({ id: 1337, jsonrpc: '2.0', method, params }) } const chainId = await this.request({ method: 'eth_chainId' }) if (!this.providers[chainId]) { const currentChain = chains.find(({ id }) => id === chainId) if (!currentChain) { throw new ProviderRpcError({ code: ProviderRpcErrorCode.CHAIN_NOT_ADDED, message: `The Provider does not have a rpcUrl to make a request for the requested method: ${method}` }) } this.providers[chainId] = new StaticJsonRpcProvider( currentChain.rpcUrl ) } return this.providers[chainId].send( method, // @ts-ignore params ) } } } return { provider: new EthProvider({ chains, connector }) } } } } } export default ledger