From c0c6c9962b882dd6c3b59073f77298c0f90fa4af Mon Sep 17 00:00:00 2001 From: Hugo Lopes Date: Wed, 21 Dec 2022 16:18:09 +0000 Subject: [PATCH 1/4] Add Ledger Connect Kit support Replaces the old USB connection aproach with the new Connect Kit. The Ledger Connect Kit loader gets the latest Connect Kit minor version from a CDN at runtime so that Ledger can release updates without waiting for wallet libraries or DApps to explicitly update packages and deploy new versions. Depending on the user's platform, Connect Kit's UI will guide them to install the Ledger Connect extension in case it is supported, or present the required details to connect to Ledger Live through WalletConnect. --- packages/ledger/package.json | 12 +- packages/ledger/src/icon.ts | 6 +- packages/ledger/src/index.ts | 622 ++++++++++++++--------------------- 3 files changed, 245 insertions(+), 395 deletions(-) diff --git a/packages/ledger/package.json b/packages/ledger/package.json index 0bb7de25e..9e85c2668 100644 --- a/packages/ledger/package.json +++ b/packages/ledger/package.json @@ -55,19 +55,13 @@ }, "license": "MIT", "devDependencies": { - "@types/ledgerhq__hw-transport-u2f": "^4.21.2", "typescript": "^4.5.5" }, "dependencies": { - "@ethereumjs/tx": "^3.4.0", "@ethersproject/providers": "^5.5.0", - "@ledgerhq/hw-app-eth": "^6.19.0", - "@ledgerhq/hw-transport-u2f": "^5.36.0-deprecated", - "@ledgerhq/hw-transport-webusb": "^6.19.0", - "@metamask/eth-sig-util": "^4.0.0", + "@ledgerhq/connect-kit-loader": "^1.0.2", + "@walletconnect/client": "^1.7.1", "@web3-onboard/common": "^2.2.3", - "@web3-onboard/hw-common": "^2.0.4", - "buffer": "^6.0.3", - "ethereumjs-util": "^7.1.3" + "rxjs": "^7.5.2" } } diff --git a/packages/ledger/src/icon.ts b/packages/ledger/src/icon.ts index 083e00e66..f89388c54 100644 --- a/packages/ledger/src/icon.ts +++ b/packages/ledger/src/icon.ts @@ -1,3 +1,5 @@ export default ` - -` + + + +` diff --git a/packages/ledger/src/index.ts b/packages/ledger/src/index.ts index d12f031bf..76d30c8cc 100644 --- a/packages/ledger/src/index.ts +++ b/packages/ledger/src/index.ts @@ -1,434 +1,288 @@ -// these cannot be dynamically imported -import { TypedDataUtils } from '@metamask/eth-sig-util' -import type Transport from '@ledgerhq/hw-transport' -import type { StaticJsonRpcProvider } from '@ethersproject/providers' -import type Eth from '@ledgerhq/hw-app-eth' - -import type { +import { Chain, WalletInit, GetInterfaceHelpers, - Platform + 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' -import type { - CustomNetwork, - ScanAccountsOptions, - Account, - Asset -} from '@web3-onboard/hw-common' - -const LEDGER_LIVE_PATH = `m/44'/60'` -const LEDGER_DEFAULT_PATH = `m/44'/60'/0'` - -const DEFAULT_BASE_PATHS = [ - { - label: 'Ledger Live', - value: LEDGER_LIVE_PATH - }, - { - label: 'Ledger Legacy', - value: LEDGER_DEFAULT_PATH +const isHexString = (value: string | number) => { + if (typeof value !== 'string' || !value.match(/^0x[0-9A-Fa-f]*$/)) { + return false } -] -const assets = [ - { - label: 'ETH' - } -] - -type CustomNavigator = Navigator & { usb: { getDevices(): void } } - -const supportsWebUSB = (): Promise => - Promise.resolve( - !!navigator && - !!(navigator as CustomNavigator).usb && - typeof (navigator as CustomNavigator).usb.getDevices === 'function' - ) - -/** - * Returns the correct ledger transport based on browser compatibility for webUSB. - * @returns - */ -const getTransport = async () => - ((await supportsWebUSB()) - ? (await import('@ledgerhq/hw-transport-webusb')).default - : (await import('@ledgerhq/hw-transport-u2f')).default - ).create() - -const getAccount = async ( - derivationPath: string, - asset: Asset, - index: number, - provider: StaticJsonRpcProvider, - eth: Eth -): Promise => { - const dPath = - derivationPath === LEDGER_LIVE_PATH - ? `${derivationPath}/${index}'/0/0` - : `${derivationPath}/${index}` - const { address } = await eth.getAddress(dPath) - return { - derivationPath: dPath, - address, - balance: { - asset: asset.label, - value: await provider.getBalance(address) - } - } + return true } -const getAddresses = async ( - derivationPath: string, - asset: Asset, - provider: StaticJsonRpcProvider, - eth: Eth -): Promise => { - const accounts = [] - let index = 0 - let zeroBalanceAccounts = 0 - - // Iterates until a 0 balance account is found - // Then adds 4 more 0 balance accounts to the array - while (zeroBalanceAccounts < 5) { - const acc = await getAccount(derivationPath, asset, index, provider, eth) - - if (acc.balance.value.isZero()) { - zeroBalanceAccounts++ - accounts.push(acc) - } else { - accounts.push(acc) - // Reset the number of 0 balance accounts - zeroBalanceAccounts = 0 - } - index++ - } - - return accounts +interface LedgerOptions { + chainId?: number + bridge?: string + infuraId?: string + rpc?: { [chainId: number]: string; } } -function ledger({ - customNetwork, - filter -}: { - customNetwork?: CustomNetwork - filter?: Platform[] -} = {}): WalletInit { - const getIcon = async () => (await import('./icon.js')).default - - return ({ device }) => { - let accounts: Account[] | undefined - - const filtered = - Array.isArray(filter) && - (filter.includes(device.type) || filter.includes(device.os.name)) - - if (filtered) return null - +function ledger(options?: LedgerOptions): WalletInit { + return () => { return { label: 'Ledger', - getIcon, - getInterface: async ({ EventEmitter, chains }: GetInterfaceHelpers) => { - const Eth = (await import('@ledgerhq/hw-app-eth')).default - const ethUtil = await import('ethereumjs-util') - - const { SignTypedDataVersion } = await import('@metamask/eth-sig-util') - const { StaticJsonRpcProvider } = await import( - '@ethersproject/providers' - ) - - const { createEIP1193Provider, ProviderRpcError } = await import( - '@web3-onboard/common' - ) - - const { accountSelect } = await import('@web3-onboard/hw-common') - - const { - getCommon, - bigNumberFieldsToStrings, - getHardwareWalletProvider - } = await import('@web3-onboard/hw-common') - - const { TransactionFactory: Transaction, Capability } = await import( - '@ethereumjs/tx' + getIcon: async () => (await import('./icon.js')).default, + getInterface: async ({ chains, EventEmitter }: GetInterfaceHelpers) => { + const { loadConnectKit, SupportedProviders, SupportedProviderImplementations } = await import( + '@ledgerhq/connect-kit-loader' ) - const transport: Transport = await getTransport() - const eth = new Eth(transport) - const eventEmitter = new EventEmitter() - - let ethersProvider: StaticJsonRpcProvider - - let currentChain: Chain = chains[0] - - const scanAccounts = async ({ - derivationPath, - chainId, - asset - }: ScanAccountsOptions): Promise => { - try { - currentChain = - chains.find(({ id }: Chain) => id === chainId) || currentChain - ethersProvider = new StaticJsonRpcProvider(currentChain.rpcUrl) - - // Checks to see if this is a custom derivation path - // If it is then just return the single account - if ( - derivationPath !== LEDGER_LIVE_PATH && - derivationPath !== LEDGER_DEFAULT_PATH - ) { - const { address } = await eth.getAddress(derivationPath) - return [ - { - derivationPath, - address, - balance: { - asset: asset.label, - value: await ethersProvider.getBalance(address) - } - } - ] - } - - const accounts = await getAddresses( - derivationPath, - asset, - ethersProvider, - eth - ) - - return accounts - } catch (error) { - const { statusText } = error as { statusText: string } - - throw new Error( - statusText === 'UNKNOWN_ERROR' - ? 'Ledger device is locked, please unlock to continue' - : statusText - ) - } - } + const connectKit = await loadConnectKit() + connectKit.enableDebugLogs() + const checkSupportResult = connectKit.checkSupport({ + providerType: SupportedProviders.Ethereum, + chainId: options?.chainId, + infuraId: options?.infuraId, + rpc: options?.rpc, + }) - const getAccounts = async () => { - accounts = await accountSelect({ - basePaths: DEFAULT_BASE_PATHS, - assets, - chains, - scanAccounts - }) + // get the Ledger provider instance, it can be either Ledger Connect + // or WalletConnect + const instance = await connectKit.getProvider() as EthereumProvider - if (accounts && accounts.length) { - eventEmitter.emit('accountsChanged', [accounts[0].address]) + // return the Ledger Connect provider + if (checkSupportResult.providerImplementation === SupportedProviderImplementations.LedgerConnect) { + return { + provider: instance as EIP1193Provider } - - return accounts } - const signMessage = async (address: string, message: string) => { - if (!(accounts && accounts.length && accounts.length > 0)) - throw new Error( - 'No account selected. Must call eth_requestAccounts first.' - ) - - const account = - accounts.find(account => account.address === address) || accounts[0] - - return eth - .signPersonalMessage( - account.derivationPath, - ethUtil.stripHexPrefix(message) - ) - .then(result => { - let v = (result['v'] - 27).toString(16) - if (v.length < 2) { - v = '0' + v + // 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 + 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 + private providers: Record + + constructor({ + connector, + chains + }: { + connector: InstanceType + 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 `0x${result['r']}${result['s']}${v}` - }) - } - const ledgerProvider = getHardwareWalletProvider( - () => currentChain?.rpcUrl - ) + 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 + }) - const provider = createEIP1193Provider(ledgerProvider, { - eth_requestAccounts: async () => { - // Triggers the account select modal if no accounts have been selected - const accounts = await getAccounts() + // listen for disconnect event + fromEvent(this.connector, 'disconnect', (error, payload) => { + if (error) { + throw error + } - if (!Array.isArray(accounts)) - throw new Error( - 'No account selected. Must call eth_requestAccounts first.' - ) - if (accounts.length === 0) { - throw new ProviderRpcError({ - code: 4001, - message: 'User rejected the request.' + return payload + }) + .pipe(takeUntil(this.disconnected$)) + .subscribe({ + next: () => { + this.emit('accountsChanged', []) + this.disconnected$.next(true) + typeof localStorage !== 'undefined' && + localStorage.removeItem('walletconnect') + }, + error: console.warn }) - } - if (!accounts[0].hasOwnProperty('address')) - throw new Error( - 'No address property associated with the selected account' - ) - return [accounts[0].address] - }, - eth_selectAccounts: async () => { - const accounts = await getAccounts() - return accounts.map(({ address }) => address) - }, - eth_accounts: async () => { - return Array.isArray(accounts) && - accounts.length && - accounts[0].hasOwnProperty('address') - ? [accounts[0].address] - : [] - }, - eth_chainId: async () => { - return (currentChain && currentChain.id) || '' - }, - eth_signTransaction: async ({ params: [transactionObject] }) => { - if (!accounts || !Array.isArray(accounts) || !accounts.length) - throw new Error( - 'No account selected. Must call eth_requestAccounts first.' - ) - let account - if (transactionObject.hasOwnProperty('from')) { - account = accounts.find( - account => account.address === transactionObject.from - ) - } - account = account ? account : accounts[0] + 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)}` + } - const { address: from, derivationPath } = account + if (method === 'eth_requestAccounts') { + return new Promise((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 - // Set the `from` field to the currently selected account - transactionObject = { ...transactionObject, from } + const hexChainId = isHexString(chainId) + ? chainId + : `0x${chainId.toString(16)}` - const chainId = currentChain.hasOwnProperty('id') - ? Number.parseInt(currentChain.id) - : 1 + this.emit('chainChanged', hexChainId) + return resolve(accounts) + } - const common = await getCommon({ customNetwork, chainId }) + // 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 + }) + }) + } - transactionObject.gasLimit = - transactionObject.gas || transactionObject.gasLimit + if (method === 'eth_selectAccounts') { + throw new ProviderRpcError({ + code: ProviderRpcErrorCode.UNSUPPORTED_METHOD, + message: `The Provider does not support the requested method: ${method}` + }) + } - // 'gas' is an invalid property for the TransactionRequest type - delete transactionObject.gas + if (method == 'wallet_switchEthereumChain') { + if (!params) { + throw new ProviderRpcError({ + code: ProviderRpcErrorCode.INVALID_PARAMS, + message: `The Provider requires a chainId to be passed in as an argument` + }) + } + const chainIdObj = params[0] as { chainId?: number } + if ( + !chainIdObj.hasOwnProperty('chainId') || + typeof chainIdObj['chainId'] === 'undefined' + ) { + throw new ProviderRpcError({ + code: ProviderRpcErrorCode.INVALID_PARAMS, + message: `The Provider requires a chainId to be passed in as an argument` + }) + } - const signer = ethersProvider.getSigner(from) + return this.connector.sendCustomRequest({ + method: 'wallet_switchEthereumChain', + params: [ + { + chainId: chainIdObj.chainId + } + ] + }) + } - let populatedTransaction = await signer.populateTransaction( - transactionObject - ) + // @ts-ignore + if (method === 'eth_sendTransaction') { + // @ts-ignore + return this.connector.sendTransaction(params[0]) + } - populatedTransaction = - bigNumberFieldsToStrings(populatedTransaction) + // @ts-ignore + if (method === 'eth_signTransaction') { + // @ts-ignore + return this.connector.signTransaction(params[0]) + } - const transaction = Transaction.fromTxData(populatedTransaction, { - common - }) + // @ts-ignore + if (method === 'personal_sign') { + // @ts-ignore + return this.connector.signPersonalMessage(params) + } - let unsignedTx = transaction.getMessageToSign(false) + // @ts-ignore + if (method === 'eth_sign') { + // @ts-ignore + return this.connector.signMessage(params) + } - // If this is not an EIP1559 transaction then it is legacy and it needs to be - // rlp encoded before being passed to ledger - if (!transaction.supports(Capability.EIP1559FeeMarket)) { - unsignedTx = ethUtil.rlp.encode(unsignedTx) - } + // @ts-ignore + if (method.includes('eth_signTypedData')) { + // @ts-ignore + return this.connector.signTypedData(params) + } - const { v, r, s } = await eth.signTransaction( - derivationPath, - unsignedTx.toString('hex') - ) - - // Reconstruct the signed transaction - const signedTx = Transaction.fromTxData( - { - ...populatedTransaction, - v: `0x${v}`, - r: `0x${r}`, - s: `0x${s}` - }, - { common } - ) - - return signedTx ? `0x${signedTx.serialize().toString('hex')}` : '' - }, - eth_sendTransaction: async ({ baseRequest, params }) => { - const signedTx = await provider.request({ - method: 'eth_signTransaction', - params - }) + if (method === 'eth_accounts') { + return this.connector.sendCustomRequest({ + id: 1337, + jsonrpc: '2.0', + method, + params + }) + } - const transactionHash = await baseRequest({ - method: 'eth_sendRawTransaction', - params: [signedTx] - }) + const chainId = await this.request({ method: 'eth_chainId' }) - return transactionHash as string - }, - eth_sign: async ({ params: [address, message] }) => - signMessage(address, message), - personal_sign: async ({ params: [message, address] }) => - signMessage(address, message), - eth_signTypedData: async ({ params: [address, typedData] }) => { - if (!(accounts && accounts.length && accounts.length > 0)) - throw new Error( - 'No account selected. Must call eth_requestAccounts first.' - ) + if (!this.providers[chainId]) { + const currentChain = chains.find(({ id }) => id === chainId) - const account = - accounts.find(account => account.address === address) || - accounts[0] - - const domainHash = TypedDataUtils.hashStruct( - 'EIP712Domain', - typedData.domain, - typedData.types, - SignTypedDataVersion.V3 - ).toString('hex') - - const messageHash = TypedDataUtils.hashStruct( - typedData.primaryType, - typedData.message, - typedData.types, - SignTypedDataVersion.V3 - ).toString('hex') - - return eth - .signEIP712HashedMessage( - account.derivationPath, - domainHash, - messageHash - ) - .then(result => { - let v = (result['v'] - 27).toString(16) - if (v.length < 2) { - v = '0' + v + 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}` + }) } - return `0x${result['r']}${result['s']}${v}` - }) - }, - wallet_switchEthereumChain: async ({ params: [{ chainId }] }) => { - currentChain = - chains.find(({ id }) => id === chainId) || currentChain - if (!currentChain) - throw new Error('chain must be set before switching') - - eventEmitter.emit('chainChanged', currentChain.id) - return null - }, - wallet_addEthereumChain: null - }) + this.providers[chainId] = new StaticJsonRpcProvider( + currentChain.rpcUrl + ) + } - provider.on = eventEmitter.on.bind(eventEmitter) + return this.providers[chainId].send( + method, + // @ts-ignore + params + ) + } + } + } return { - provider + provider: new EthProvider({ chains, connector }) } } } From adfffeaf44d3f2fb63e58711bf090580deb0b0b0 Mon Sep 17 00:00:00 2001 From: Adam Carpenter Date: Wed, 4 Jan 2023 14:45:54 -0700 Subject: [PATCH 2/4] Prettier and version bumps --- packages/demo/package.json | 2 +- packages/ledger/package.json | 2 +- packages/ledger/src/index.ts | 29 ++++--- yarn.lock | 148 ++--------------------------------- 4 files changed, 29 insertions(+), 152 deletions(-) diff --git a/packages/demo/package.json b/packages/demo/package.json index 943ef5ce9..6e4992d35 100644 --- a/packages/demo/package.json +++ b/packages/demo/package.json @@ -33,7 +33,7 @@ "@web3-onboard/injected-wallets": "^2.5.0-alpha.1", "@web3-onboard/keepkey": "^2.3.2", "@web3-onboard/keystone": "^2.3.2", - "@web3-onboard/ledger": "^2.3.2", + "@web3-onboard/ledger": "^2.4.0-alpha.1", "@web3-onboard/magic": "^2.1.3", "@web3-onboard/phantom": "^2.0.0-alpha.1", "@web3-onboard/portis": "^2.1.3", diff --git a/packages/ledger/package.json b/packages/ledger/package.json index 9e85c2668..f0da8e32d 100644 --- a/packages/ledger/package.json +++ b/packages/ledger/package.json @@ -1,6 +1,6 @@ { "name": "@web3-onboard/ledger", - "version": "2.3.2", + "version": "2.4.0-alpha.1", "description": "Ledger hardare wallet module for connecting to Web3-Onboard. 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, multi-chain and multi-account support, reactive wallet state subscriptions and real-time transaction state change notifications.", "keywords": [ "Ethereum", diff --git a/packages/ledger/src/index.ts b/packages/ledger/src/index.ts index 76d30c8cc..2ec9498d0 100644 --- a/packages/ledger/src/index.ts +++ b/packages/ledger/src/index.ts @@ -3,7 +3,7 @@ import { WalletInit, GetInterfaceHelpers, EIP1193Provider, - ProviderAccounts, + ProviderAccounts } from '@web3-onboard/common' import type { EthereumProvider } from '@ledgerhq/connect-kit-loader' import type { StaticJsonRpcProvider as StaticJsonRpcProviderType } from '@ethersproject/providers' @@ -21,7 +21,7 @@ interface LedgerOptions { chainId?: number bridge?: string infuraId?: string - rpc?: { [chainId: number]: string; } + rpc?: { [chainId: number]: string } } function ledger(options?: LedgerOptions): WalletInit { @@ -30,9 +30,11 @@ function ledger(options?: LedgerOptions): WalletInit { 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 { + loadConnectKit, + SupportedProviders, + SupportedProviderImplementations + } = await import('@ledgerhq/connect-kit-loader') const connectKit = await loadConnectKit() connectKit.enableDebugLogs() @@ -40,23 +42,30 @@ function ledger(options?: LedgerOptions): WalletInit { providerType: SupportedProviders.Ethereum, chainId: options?.chainId, infuraId: options?.infuraId, - rpc: options?.rpc, + rpc: options?.rpc }) // get the Ledger provider instance, it can be either Ledger Connect // or WalletConnect - const instance = await connectKit.getProvider() as EthereumProvider + const instance = (await connectKit.getProvider()) as EthereumProvider // return the Ledger Connect provider - if (checkSupportResult.providerImplementation === SupportedProviderImplementations.LedgerConnect) { + 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 { 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') diff --git a/yarn.lock b/yarn.lock index ed6b90532..1406f3787 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1402,104 +1402,10 @@ rxjs "^6.6.3" typescript "^4.6.2" -"@ledgerhq/cryptoassets@^6.25.0": - version "6.25.0" - resolved "https://registry.yarnpkg.com/@ledgerhq/cryptoassets/-/cryptoassets-6.25.0.tgz#9e9307c69c436c938fafd27d5351526c21a2a114" - integrity sha512-WjE94BGv9bh70UrgAKH3mAflnAYUGnln/GNEP3UWJKtCkE64RCvRYFI9J4OpnSLEIs9Yf9h48Mjbu6Q2CYao+Q== - dependencies: - invariant "2" - -"@ledgerhq/devices@^5.51.1": - version "5.51.1" - resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-5.51.1.tgz#d741a4a5d8f17c2f9d282fd27147e6fe1999edb7" - integrity sha512-4w+P0VkbjzEXC7kv8T1GJ/9AVaP9I6uasMZ/JcdwZBS3qwvKo5A5z9uGhP5c7TvItzcmPb44b5Mw2kT+WjUuAA== - dependencies: - "@ledgerhq/errors" "^5.50.0" - "@ledgerhq/logs" "^5.50.0" - rxjs "6" - semver "^7.3.5" - -"@ledgerhq/devices@^6.24.1": - version "6.24.1" - resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-6.24.1.tgz#9696d7831aa1a1a8204cdfa55df13f892b7da162" - integrity sha512-6SNXWXxojUF6WKXMVIbRs15Mveg+9k0RKJK/PKlwZh929Lnr/NcbONWdwPjWKZAp1g82eEPT4jIkG6qc4QXlcA== - dependencies: - "@ledgerhq/errors" "^6.10.0" - "@ledgerhq/logs" "^6.10.0" - rxjs "6" - semver "^7.3.5" - -"@ledgerhq/errors@^5.34.0", "@ledgerhq/errors@^5.50.0": - version "5.50.0" - resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-5.50.0.tgz#e3a6834cb8c19346efca214c1af84ed28e69dad9" - integrity sha512-gu6aJ/BHuRlpU7kgVpy2vcYk6atjB4iauP2ymF7Gk0ez0Y/6VSMVSJvubeEQN+IV60+OBK0JgeIZG7OiHaw8ow== - -"@ledgerhq/errors@^6.10.0": - version "6.10.0" - resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-6.10.0.tgz#dda9127b65f653fbb2f74a55e8f0e550d69de6e4" - integrity sha512-fQFnl2VIXh9Yd41lGjReCeK+Q2hwxQJvLZfqHnKqWapTz68NHOv5QcI0OHuZVNEbv0xhgdLhi5b65kgYeQSUVg== - -"@ledgerhq/hw-app-eth@^6.19.0": - version "6.25.0" - resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-eth/-/hw-app-eth-6.25.0.tgz#afdf9c45ce24a725dda98805785492262d83b16f" - integrity sha512-7oFRWSWLKE0uZosWaCsBC83avCfHZKoqPw36iAj6UhylbICqszOOL9RNa4b1TUnEMI0K2/W0hGPlGGYY2/5g9Q== - dependencies: - "@ethersproject/abi" "^5.5.0" - "@ethersproject/rlp" "^5.5.0" - "@ledgerhq/cryptoassets" "^6.25.0" - "@ledgerhq/errors" "^6.10.0" - "@ledgerhq/hw-transport" "^6.24.1" - "@ledgerhq/logs" "^6.10.0" - axios "^0.24.0" - bignumber.js "^9.0.2" - -"@ledgerhq/hw-transport-u2f@^5.36.0-deprecated": - version "5.36.0-deprecated" - resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-u2f/-/hw-transport-u2f-5.36.0-deprecated.tgz#66e3ed399a117a1c0110871a055dd54f5fe707fd" - integrity sha512-T/+mGHIiUK/ZQATad6DMDmobCMZ1mVST952009jKzhaE1Et2Uy2secU+QhRkx3BfEAkvwa0zSRSYCL9d20Iqjg== - dependencies: - "@ledgerhq/errors" "^5.34.0" - "@ledgerhq/hw-transport" "^5.34.0" - "@ledgerhq/logs" "^5.30.0" - u2f-api "0.2.7" - -"@ledgerhq/hw-transport-webusb@^6.19.0": - version "6.24.1" - resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-webusb/-/hw-transport-webusb-6.24.1.tgz#9267b6cb23ba991ce3a5debb15d162125a772b1b" - integrity sha512-+bAkVF/5MbbGIXobtmc5st/gFEjSRqACk+UPJGSxT21Z2SVm+FgG0Bui5wy24H+Ts/tC4IA3Mff8cz4PGbZhPA== - dependencies: - "@ledgerhq/devices" "^6.24.1" - "@ledgerhq/errors" "^6.10.0" - "@ledgerhq/hw-transport" "^6.24.1" - "@ledgerhq/logs" "^6.10.0" - -"@ledgerhq/hw-transport@^5.34.0": - version "5.51.1" - resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-5.51.1.tgz#8dd14a8e58cbee4df0c29eaeef983a79f5f22578" - integrity sha512-6wDYdbWrw9VwHIcoDnqWBaDFyviyjZWv6H9vz9Vyhe4Qd7TIFmbTl/eWs6hZvtZBza9K8y7zD8ChHwRI4s9tSw== - dependencies: - "@ledgerhq/devices" "^5.51.1" - "@ledgerhq/errors" "^5.50.0" - events "^3.3.0" - -"@ledgerhq/hw-transport@^6.24.1": - version "6.24.1" - resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-6.24.1.tgz#5e787268e6d5ce4f9f0d53b0d520c1f071c2d1ae" - integrity sha512-cOhxkQJrN7DvPFLLXAS2nqAZ7NIDaFqnbgu9ugTccgbJm2/z7ClRZX/uQoI4FscswZ47MuJQdXqz4nK48phteQ== - dependencies: - "@ledgerhq/devices" "^6.24.1" - "@ledgerhq/errors" "^6.10.0" - events "^3.3.0" - -"@ledgerhq/logs@^5.30.0", "@ledgerhq/logs@^5.50.0": - version "5.50.0" - resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-5.50.0.tgz#29c6419e8379d496ab6d0426eadf3c4d100cd186" - integrity sha512-swKHYCOZUGyVt4ge0u8a7AwNcA//h4nx5wIi0sruGye1IJ5Cva0GyK9L2/WdX+kWVTKp92ZiEo1df31lrWGPgA== - -"@ledgerhq/logs@^6.10.0": - version "6.10.0" - resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-6.10.0.tgz#c012c1ecc1a0e53d50e6af381618dca5268461c1" - integrity sha512-lLseUPEhSFUXYTKj6q7s2O3s2vW2ebgA11vMAlKodXGf5AFw4zUoEbTz9CoFOC9jS6xY4Qr8BmRnxP/odT4Uuw== +"@ledgerhq/connect-kit-loader@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@ledgerhq/connect-kit-loader/-/connect-kit-loader-1.0.2.tgz#8554e16943f86cc2a5f6348a14dfe6e5bd0c572a" + integrity sha512-TQ21IjcZOw/scqypaVFY3jHVqI7X7Hta3qN/us6FvTol3AY06UmrhhXGww0E9xHmAbdX241ddwXEiMBSQZFr9g== "@magic-sdk/commons@^4.1.0": version "4.1.0" @@ -1525,17 +1431,6 @@ resolved "https://registry.yarnpkg.com/@metamask/detect-provider/-/detect-provider-2.0.0.tgz#4bc2795e5e6f7d8b84b2e845058d2f222c99917d" integrity sha512-sFpN+TX13E9fdBDh9lvQeZdJn4qYoRb/6QF2oZZK/Pn559IhCFacPMU1rMuqyXoFQF3JSJfii2l98B87QDPeCQ== -"@metamask/eth-sig-util@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@metamask/eth-sig-util/-/eth-sig-util-4.0.0.tgz#11553ba06de0d1352332c1bde28c8edd00e0dcf6" - integrity sha512-LczOjjxY4A7XYloxzyxJIHONELmUxVZncpOLoClpEcTiebiVdM46KRPYXGuULro9oNNR2xdVx3yoKiQjdfWmoA== - dependencies: - ethereumjs-abi "^0.6.8" - ethereumjs-util "^6.2.1" - ethjs-util "^0.1.6" - tweetnacl "^1.0.3" - tweetnacl-util "^0.15.1" - "@metamask/eth-sig-util@^5.0.2": version "5.0.2" resolved "https://registry.yarnpkg.com/@metamask/eth-sig-util/-/eth-sig-util-5.0.2.tgz#c518279a6e17a88135a13d53a0b970f145ff8bce" @@ -2308,21 +2203,6 @@ dependencies: "@types/node" "*" -"@types/ledgerhq__hw-transport-u2f@^4.21.2": - version "4.21.2" - resolved "https://registry.yarnpkg.com/@types/ledgerhq__hw-transport-u2f/-/ledgerhq__hw-transport-u2f-4.21.2.tgz#2e24c8b235f662177b6e9033c50258dcba1e4109" - integrity sha512-xHLa7bKY9wWqbqYraLBg+yLQqTlk9D5SFUgKXNOlRlQmt02hKgvbOHkAL1PQ+pfpTWniuC7BgBYjCObu6fgGzA== - dependencies: - "@types/ledgerhq__hw-transport" "*" - "@types/node" "*" - -"@types/ledgerhq__hw-transport@*": - version "4.21.4" - resolved "https://registry.yarnpkg.com/@types/ledgerhq__hw-transport/-/ledgerhq__hw-transport-4.21.4.tgz#3a78a02d2b51d2b0dd8099412d5567d21118225c" - integrity sha512-vep+6yZnGv6owAthIY0w3f72w4dJIb4+yE5PCHveInTlZE9wukvU6Wc5Eig0OUUxcdhTazzeZx1xUaNVLqyQSg== - dependencies: - "@types/node" "*" - "@types/lodash.merge@^4.6.6": version "4.6.6" resolved "https://registry.yarnpkg.com/@types/lodash.merge/-/lodash.merge-4.6.6.tgz#b84b403c1d31bc42d51772d1cd5557fa008cd3d6" @@ -3677,7 +3557,7 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== -axios@0.21.1, axios@^0.18.0, axios@^0.21.2, axios@^0.24.0: +axios@0.21.1, axios@^0.18.0, axios@^0.21.2: version "0.21.4" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== @@ -3798,7 +3678,7 @@ bigint-buffer@^1.1.5: dependencies: bindings "^1.3.0" -bignumber.js@^9.0.0, bignumber.js@^9.0.1, bignumber.js@^9.0.2: +bignumber.js@^9.0.0, bignumber.js@^9.0.1: version "9.0.2" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.2.tgz#71c6c6bed38de64e24a65ebe16cfcf23ae693673" integrity sha512-GAcQvbpsM0pUb0zw1EI0KhQEZ+lRwR5fYaAp3vPOYuP7aDvGy6cVN6XHLauvF8SOga2y0dcLcjt3iQDTSEliyw== @@ -5840,7 +5720,7 @@ ethereumjs-util@^5.0.0, ethereumjs-util@^5.1.1, ethereumjs-util@^5.1.2, ethereum rlp "^2.0.0" safe-buffer "^5.1.1" -ethereumjs-util@^6.0.0, ethereumjs-util@^6.2.1: +ethereumjs-util@^6.0.0: version "6.2.1" resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz#fcb4e4dd5ceacb9d2305426ab1a5cd93e3163b69" integrity sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw== @@ -6950,13 +6830,6 @@ intl-messageformat@^9.3.15: "@formatjs/icu-messageformat-parser" "2.0.18" tslib "^2.1.0" -invariant@2: - version "2.2.4" - resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" - integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== - dependencies: - loose-envify "^1.0.0" - ip@^1.1.0: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" @@ -9403,7 +9276,7 @@ rustbn.js@~0.2.0: resolved "https://registry.yarnpkg.com/rustbn.js/-/rustbn.js-0.2.0.tgz#8082cb886e707155fd1cb6f23bd591ab8d55d0ca" integrity sha512-4VlvkRUuCJvr2J6Y0ImW7NvTCriMi7ErOAqWk1y69vAdoNIzCF3yPmgeNzx+RQTLEDFq5sHfscn1MwHxP9hNfA== -rxjs@6, rxjs@^6.4.0, rxjs@^6.6.3: +rxjs@^6.4.0, rxjs@^6.6.3: version "6.6.7" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== @@ -10572,11 +10445,6 @@ typescript@^4.6.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9" integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg== -u2f-api@0.2.7: - version "0.2.7" - resolved "https://registry.yarnpkg.com/u2f-api/-/u2f-api-0.2.7.tgz#17bf196b242f6bf72353d9858e6a7566cc192720" - integrity sha512-fqLNg8vpvLOD5J/z4B6wpPg4Lvowz1nJ9xdHcCzdUPKcFE/qNCceV2gNZxSJd5vhAZemHr/K/hbzVA0zxB5mkg== - ultron@~1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" From 2cf23a8a27b3c53e272b5fe8b79ccc3582ef2091 Mon Sep 17 00:00:00 2001 From: Hugo Lopes Date: Thu, 5 Jan 2023 14:03:12 +0000 Subject: [PATCH 3/4] Do not support switching chains from DApp on Ledger connector --- packages/ledger/src/index.ts | 27 +++------------------------ 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/packages/ledger/src/index.ts b/packages/ledger/src/index.ts index 2ec9498d0..445d0218b 100644 --- a/packages/ledger/src/index.ts +++ b/packages/ledger/src/index.ts @@ -198,30 +198,9 @@ function ledger(options?: LedgerOptions): WalletInit { } if (method == 'wallet_switchEthereumChain') { - if (!params) { - throw new ProviderRpcError({ - code: ProviderRpcErrorCode.INVALID_PARAMS, - message: `The Provider requires a chainId to be passed in as an argument` - }) - } - const chainIdObj = params[0] as { chainId?: number } - if ( - !chainIdObj.hasOwnProperty('chainId') || - typeof chainIdObj['chainId'] === 'undefined' - ) { - throw new ProviderRpcError({ - code: ProviderRpcErrorCode.INVALID_PARAMS, - message: `The Provider requires a chainId to be passed in as an argument` - }) - } - - return this.connector.sendCustomRequest({ - method: 'wallet_switchEthereumChain', - params: [ - { - chainId: chainIdObj.chainId - } - ] + throw new ProviderRpcError({ + code: ProviderRpcErrorCode.UNSUPPORTED_METHOD, + message: `The Provider does not support the requested method: ${method}` }) } From 0aaab924cc8249968dd2631471ef062b404d5286 Mon Sep 17 00:00:00 2001 From: Adam Carpenter Date: Thu, 5 Jan 2023 08:47:25 -0700 Subject: [PATCH 4/4] Add modal prompt for handling switchEthereumChain to switch chain in the wallet app --- packages/ledger/src/index.ts | 28 +++------------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/packages/ledger/src/index.ts b/packages/ledger/src/index.ts index 2ec9498d0..e39f951ba 100644 --- a/packages/ledger/src/index.ts +++ b/packages/ledger/src/index.ts @@ -181,7 +181,6 @@ function ledger(options?: LedgerOptions): WalletInit { const hexChainId = isHexString(chainId) ? chainId : `0x${chainId.toString(16)}` - this.emit('chainChanged', hexChainId) resolve(accounts) }, @@ -198,30 +197,9 @@ function ledger(options?: LedgerOptions): WalletInit { } if (method == 'wallet_switchEthereumChain') { - if (!params) { - throw new ProviderRpcError({ - code: ProviderRpcErrorCode.INVALID_PARAMS, - message: `The Provider requires a chainId to be passed in as an argument` - }) - } - const chainIdObj = params[0] as { chainId?: number } - if ( - !chainIdObj.hasOwnProperty('chainId') || - typeof chainIdObj['chainId'] === 'undefined' - ) { - throw new ProviderRpcError({ - code: ProviderRpcErrorCode.INVALID_PARAMS, - message: `The Provider requires a chainId to be passed in as an argument` - }) - } - - return this.connector.sendCustomRequest({ - method: 'wallet_switchEthereumChain', - params: [ - { - chainId: chainIdObj.chainId - } - ] + throw new ProviderRpcError({ + code: ProviderRpcErrorCode.UNSUPPORTED_METHOD, + message: `The Provider does not support the requested method: ${method}` }) }