From eed01c97353f5453bad33572036e814bb9620281 Mon Sep 17 00:00:00 2001 From: Erik Marks <25517051+rekmarks@users.noreply.github.com> Date: Mon, 10 Feb 2020 17:45:53 -0800 Subject: [PATCH] Reorganize RPC middlewares; update mobile-provider (#1307) BackgroundBridge constructor reorg update eth-json-rpc-errors --- app/components/Views/BrowserTab/index.js | 531 ++++++++++++----------- app/core/BackgroundBridge.js | 51 +-- app/util/middlewares.js | 7 + package.json | 4 +- yarn.lock | 24 +- 5 files changed, 309 insertions(+), 308 deletions(-) diff --git a/app/components/Views/BrowserTab/index.js b/app/components/Views/BrowserTab/index.js index 92539e6560e..334501ac53d 100644 --- a/app/components/Views/BrowserTab/index.js +++ b/app/components/Views/BrowserTab/index.js @@ -61,7 +61,7 @@ import DrawerStatusTracker from '../../../core/DrawerStatusTracker'; import { resemblesAddress } from '../../../util/address'; import createAsyncMiddleware from 'json-rpc-engine/src/createAsyncMiddleware'; -import { errors as rpcErrors } from 'eth-json-rpc-errors'; +import { ethErrors } from 'eth-json-rpc-errors'; const { HOMEPAGE_URL, USER_AGENT } = AppConstants; const HOMEPAGE_HOST = 'home.metamask.io'; @@ -451,273 +451,274 @@ export class BrowserTab extends PureComponent { } initializeBackgroundBridge = (url, isMainFrame) => { - const newBridge = new BackgroundBridge( - this.webview, + const newBridge = new BackgroundBridge({ + webview: this.webview, url, - { - // ALL USER FACING RPC CALLS HERE - eth_requestAccounts: senderUrl => - createAsyncMiddleware(async (req, res, next) => { - if (req.method !== 'eth_requestAccounts') return next(); - const { hostname } = senderUrl; - const { params } = req; - const { approvedHosts, privacyMode, selectedAddress } = this.props; - if (!privacyMode || ((!params || !params.force) && approvedHosts[hostname])) { - res.result = [selectedAddress]; - } else { - if (!this.state.showApprovalDialog) { - setTimeout(async () => { - if (!this.state.showApprovalDialog) { - await this.getPageMeta(); - this.setState({ - showApprovalDialog: true, - showApprovalDialogHostname: hostname - }); - } - }, 1000); - } - const approved = await new Promise((resolve, reject) => { - this.approvalRequest = { resolve, reject }; - }); - if (approved) { - res.result = [selectedAddress.toLowerCase()]; - this.backgroundBridges.forEach(bridge => { - if (bridge.hostname === senderUrl.hostname) { - bridge.emit('update'); - } - }); - } else { - throw rpcErrors.eth.userRejectedRequest('User denied account authorization'); - } + getRpcMethodMiddleware: this.getRpcMethodMiddleware.bind(this), + shouldExposeAccounts: hostname => { + const { approvedHosts, privacyMode } = this.props; + return !privacyMode || approvedHosts[hostname]; + }, + isMainFrame + }); + this.backgroundBridges.push(newBridge); + }; + + getRpcMethodMiddleware = ({ hostname }) => + // all user facing RPC calls not implemented by the provider + createAsyncMiddleware(async (req, res, next) => { + const rpcMethods = { + eth_requestAccounts: async () => { + const { params } = req; + const { approvedHosts, privacyMode, selectedAddress } = this.props; + + if (!privacyMode || ((!params || !params.force) && approvedHosts[hostname])) { + res.result = [selectedAddress]; + } else { + if (!this.state.showApprovalDialog) { + setTimeout(async () => { + if (!this.state.showApprovalDialog) { + await this.getPageMeta(); + this.setState({ + showApprovalDialog: true, + showApprovalDialogHostname: hostname + }); + } + }, 1000); // TODO: how long does this actually have to be? } - }), - - eth_accounts: senderUrl => - createAsyncMiddleware(async (req, res, next) => { - if (req.method !== 'eth_accounts') return next(); - const { hostname } = senderUrl; - const { approvedHosts, privacyMode, selectedAddress } = this.props; - const isEnabled = !privacyMode || approvedHosts[hostname]; - if (isEnabled) { + + const approved = await new Promise((resolve, reject) => { + this.approvalRequest = { resolve, reject }; + }); + + if (approved) { res.result = [selectedAddress.toLowerCase()]; + this.backgroundBridges.forEach(bridge => { + if (bridge.hostname === hostname) { + bridge.emit('update'); + } + }); } else { - res.result = []; + throw ethErrors.provider.userRejectedRequest('User denied account authorization.'); } - }), - - eth_sign: () => - createAsyncMiddleware(async (req, res, next) => { - if (req.method !== 'eth_sign') return next(); - const { MessageManager } = Engine.context; - const pageMeta = await this.getPageMeta(); - const rawSig = await MessageManager.addUnapprovedMessageAsync({ - data: req.params[1], - from: req.params[0], - ...pageMeta - }); - res.result = rawSig; - }), - - personal_sign: () => - createAsyncMiddleware(async (req, res, next) => { - if (req.method !== 'personal_sign') return next(); - const { PersonalMessageManager } = Engine.context; - const firstParam = req.params[0]; - const secondParam = req.params[1]; - const params = { - data: firstParam, - from: secondParam - }; - if (resemblesAddress(firstParam) && !resemblesAddress(secondParam)) { - params.data = secondParam; - params.from = firstParam; - } - const pageMeta = await this.getPageMeta(); - const rawSig = await PersonalMessageManager.addUnapprovedMessageAsync({ - ...params, - ...pageMeta - }); + } + }, - res.result = rawSig; - }), - eth_signTypedData: () => - createAsyncMiddleware(async (req, res, next) => { - if (req.method !== 'eth_signTypedData') return next(); - const { TypedMessageManager } = Engine.context; - const pageMeta = await this.getPageMeta(); - const rawSig = await TypedMessageManager.addUnapprovedMessageAsync( - { - data: req.params[0], - from: req.params[1], - ...pageMeta - }, - 'V1' - ); + eth_accounts: async () => { + const { approvedHosts, privacyMode, selectedAddress } = this.props; + const isEnabled = !privacyMode || approvedHosts[hostname]; - res.result = rawSig; - }), - eth_signTypedData_v3: () => - createAsyncMiddleware(async (req, res, next) => { - if (req.method !== 'eth_signTypedData_v3') return next(); - const { TypedMessageManager } = Engine.context; - const data = JSON.parse(req.params[1]); - const chainId = data.domain.chainId; - const activeChainId = - this.props.networkType === 'rpc' - ? this.props.network - : Networks[this.props.networkType].networkId; - - // eslint-disable-next-line eqeqeq - if (chainId && chainId != activeChainId) { - throw rpcErrors.eth.userRejectedRequest( - `Provided chainId (${chainId}) must match the active chainId (${activeChainId})` - ); - } + if (isEnabled) { + res.result = [selectedAddress.toLowerCase()]; + } else { + res.result = []; + } + }, + + eth_sign: async () => { + const { MessageManager } = Engine.context; + const pageMeta = await this.getPageMeta(); + const rawSig = await MessageManager.addUnapprovedMessageAsync({ + data: req.params[1], + from: req.params[0], + ...pageMeta + }); - const pageMeta = await this.getPageMeta(); - const rawSig = await TypedMessageManager.addUnapprovedMessageAsync( - { - data: req.params[1], - from: req.params[0], - ...pageMeta - }, - 'V3' - ); + res.result = rawSig; + }, + + personal_sign: async () => { + const { PersonalMessageManager } = Engine.context; + const firstParam = req.params[0]; + const secondParam = req.params[1]; + const params = { + data: firstParam, + from: secondParam + }; + + if (resemblesAddress(firstParam) && !resemblesAddress(secondParam)) { + params.data = secondParam; + params.from = firstParam; + } - res.result = rawSig; - }), - eth_signTypedData_v4: () => - createAsyncMiddleware(async (req, res, next) => { - if (req.method !== 'eth_signTypedData_v4') return next(); - const { TypedMessageManager } = Engine.context; - const data = JSON.parse(req.params[1]); - const chainId = data.domain.chainId; - const activeChainId = - this.props.networkType === 'rpc' - ? this.props.network - : Networks[this.props.networkType].networkId; - - // eslint-disable-next-line eqeqeq - if (chainId && chainId != activeChainId) { - throw rpcErrors.eth.userRejectedRequest( - `Provided chainId (${chainId}) must match the active chainId (${activeChainId})` - ); - } + const pageMeta = await this.getPageMeta(); + const rawSig = await PersonalMessageManager.addUnapprovedMessageAsync({ + ...params, + ...pageMeta + }); - const pageMeta = await this.getPageMeta(); - const rawSig = await TypedMessageManager.addUnapprovedMessageAsync( - { - data: req.params[1], - from: req.params[0], - ...pageMeta - }, - 'V4' + res.result = rawSig; + }, + + eth_signTypedData: async () => { + const { TypedMessageManager } = Engine.context; + const pageMeta = await this.getPageMeta(); + const rawSig = await TypedMessageManager.addUnapprovedMessageAsync( + { + data: req.params[0], + from: req.params[1], + ...pageMeta + }, + 'V1' + ); + + res.result = rawSig; + }, + + eth_signTypedData_v3: async () => { + const { TypedMessageManager } = Engine.context; + const data = JSON.parse(req.params[1]); + const chainId = data.domain.chainId; + const activeChainId = + this.props.networkType === 'rpc' + ? this.props.network + : Networks[this.props.networkType].networkId; + + // eslint-disable-next-line eqeqeq + if (chainId && chainId != activeChainId) { + throw ethErrors.rpc.invalidRequest( + `Provided chainId (${chainId}) must match the active chainId (${activeChainId})` ); + } - res.result = rawSig; - }), - web3_clientVersion: () => - createAsyncMiddleware(async (req, res, next) => { - if (req.method !== 'web3_clientVersion') return next(); - res.result = `MetaMask/${this.props.app_version}/Beta/Mobile`; - }), - wallet_scanQRCode: () => - createAsyncMiddleware(async (req, res, next) => { - if (req.method !== 'wallet_scanQRCode') return next(); - this.props.navigation.navigate('QRScanner', { - onScanSuccess: data => { - let result = data; - if (data.target_address) { - result = data.target_address; - } else if (data.scheme) { - result = JSON.stringify(data); - } - res.result = result; - }, - onScanError: e => { - throw rpcErrors.eth.userRejectedRequest(e.toString()); - } - }); - }), - wallet_watchAsset: () => - createAsyncMiddleware(async (req, res, next) => { - if (req.method !== 'wallet_watchAsset') return next(); - const { - options: { address, decimals, image, symbol }, - type - } = req; - const { AssetsController } = Engine.context; - const suggestionResult = await AssetsController.watchAsset( - { address, symbol, decimals, image }, - type + const pageMeta = await this.getPageMeta(); + const rawSig = await TypedMessageManager.addUnapprovedMessageAsync( + { + data: req.params[1], + from: req.params[0], + ...pageMeta + }, + 'V3' + ); + + res.result = rawSig; + }, + + eth_signTypedData_v4: async () => { + const { TypedMessageManager } = Engine.context; + const data = JSON.parse(req.params[1]); + const chainId = data.domain.chainId; + const activeChainId = + this.props.networkType === 'rpc' + ? this.props.network + : Networks[this.props.networkType].networkId; + + // eslint-disable-next-line eqeqeq + if (chainId && chainId != activeChainId) { + throw ethErrors.rpc.invalidRequest( + `Provided chainId (${chainId}) must match the active chainId (${activeChainId})` ); - res.result = suggestionResult.result; - }), - metamask_removeFavorite: () => - createAsyncMiddleware(async (req, res, next) => { - if (req.method !== 'metamask_removeFavorite') return next(); - if (!this.isHomepage()) { - throw rpcErrors.eth.userRejectedRequest('unauthorized'); - } + } - Alert.alert(strings('browser.remove_bookmark_title'), strings('browser.remove_bookmark_msg'), [ - { - text: strings('browser.cancel'), - onPress: () => { - res.result = { - favorites: this.props.bookmarks - }; - }, - style: 'cancel' - }, - { - text: strings('browser.yes'), - onPress: () => { - const bookmark = { url: req[0] }; - this.props.removeBookmark(bookmark); - // remove bookmark from homepage - this.refreshHomeScripts(); - res.result = { - favorites: this.props.bookmarks - }; - } + const pageMeta = await this.getPageMeta(); + const rawSig = await TypedMessageManager.addUnapprovedMessageAsync( + { + data: req.params[1], + from: req.params[0], + ...pageMeta + }, + 'V4' + ); + + res.result = rawSig; + }, + + web3_clientVersion: async () => { + res.result = `MetaMask/${this.props.app_version}/Beta/Mobile`; + }, + + wallet_scanQRCode: async () => { + this.props.navigation.navigate('QRScanner', { + onScanSuccess: data => { + let result = data; + if (data.target_address) { + result = data.target_address; + } else if (data.scheme) { + result = JSON.stringify(data); } - ]); - }), - metamask_showTutorial: () => - createAsyncMiddleware(async (req, res, next) => { - if (req.method !== 'metamask_showTutorial') return next(); - this.wizardScrollAdjusted = false; - this.props.setOnboardingWizardStep(1); - this.props.navigation.navigate('WalletView'); - res.result = true; - }), - metamask_showAutocomplete: () => - createAsyncMiddleware(async (req, res, next) => { - if (req.method !== 'metamask_showAutocomplete') return next(); - this.fromHomepage = true; - this.setState( - { - autocompleteInputValue: '' + res.result = result; + }, + onScanError: e => { + throw ethErrors.rpc.internal(e.toString()); + } + }); + }, + + wallet_watchAsset: async () => { + const { + options: { address, decimals, image, symbol }, + type + } = req; + const { AssetsController } = Engine.context; + const suggestionResult = await AssetsController.watchAsset( + { address, symbol, decimals, image }, + type + ); + + res.result = suggestionResult.result; + }, + + metamask_removeFavorite: async () => { + if (!this.isHomepage()) { + throw ethErrors.provider.unauthorized('Forbidden.'); + } + + Alert.alert(strings('browser.remove_bookmark_title'), strings('browser.remove_bookmark_msg'), [ + { + text: strings('browser.cancel'), + onPress: () => { + res.result = { + favorites: this.props.bookmarks + }; }, - () => { - this.showUrlModal(true); - setTimeout(() => { - this.fromHomepage = false; - }, 1500); + style: 'cancel' + }, + { + text: strings('browser.yes'), + onPress: () => { + const bookmark = { url: req[0] }; + this.props.removeBookmark(bookmark); + // remove bookmark from homepage + this.refreshHomeScripts(); + res.result = { + favorites: this.props.bookmarks + }; } - ); - res.result = true; - }) - }, - hostname => { - const { approvedHosts, privacyMode } = this.props; - return !privacyMode || approvedHosts[hostname]; - }, - isMainFrame - ); - this.backgroundBridges.push(newBridge); - }; + } + ]); + }, + + metamask_showTutorial: async () => { + this.wizardScrollAdjusted = false; + this.props.setOnboardingWizardStep(1); + this.props.navigation.navigate('WalletView'); + + res.result = true; + }, + + metamask_showAutocomplete: async () => { + this.fromHomepage = true; + this.setState( + { + autocompleteInputValue: '' + }, + () => { + this.showUrlModal(true); + setTimeout(() => { + this.fromHomepage = false; + }, 1500); + } + ); + + res.result = true; + } + }; + + if (!rpcMethods[req.method]) { + return next(); + } + await rpcMethods[req.method](); + }); init = async () => { const entryScriptWeb3 = @@ -728,10 +729,10 @@ export class BrowserTab extends PureComponent { const analyticsEnabled = Analytics.getEnabled(); const homepageScripts = ` - window.__mmFavorites = ${JSON.stringify(this.props.bookmarks)}; - window.__mmSearchEngine = "${this.props.searchEngine}"; - window.__mmMetametrics = ${analyticsEnabled}; - `; + window.__mmFavorites = ${JSON.stringify(this.props.bookmarks)}; + window.__mmSearchEngine = "${this.props.searchEngine}"; + window.__mmMetametrics = ${analyticsEnabled}; + `; await this.setState({ entryScriptWeb3: entryScriptWeb3 + SPA_urlChangeListener, homepageScripts }); Engine.context.AssetsController.hub.on('pendingSuggestedAsset', suggestedAssetMeta => { @@ -828,11 +829,11 @@ export class BrowserTab extends PureComponent { const analyticsEnabled = Analytics.getEnabled(); const homepageScripts = ` - window.__mmFavorites = ${JSON.stringify(this.props.bookmarks)}; - window.__mmSearchEngine="${this.props.searchEngine}"; - window.__mmMetametrics = ${analyticsEnabled}; - window.postMessage('updateFavorites', '*'); - `; + window.__mmFavorites = ${JSON.stringify(this.props.bookmarks)}; + window.__mmSearchEngine="${this.props.searchEngine}"; + window.__mmMetametrics = ${analyticsEnabled}; + window.postMessage('updateFavorites', '*'); + `; this.setState({ homepageScripts }, () => { const { current } = this.webview; if (current) { @@ -1146,10 +1147,10 @@ export class BrowserTab extends PureComponent { const analyticsEnabled = Analytics.getEnabled(); const homepageScripts = ` - window.__mmFavorites = ${JSON.stringify(this.props.bookmarks)}; - window.__mmSearchEngine="${this.props.searchEngine}"; - window.__mmMetametrics = ${analyticsEnabled}; - `; + window.__mmFavorites = ${JSON.stringify(this.props.bookmarks)}; + window.__mmSearchEngine="${this.props.searchEngine}"; + window.__mmMetametrics = ${analyticsEnabled}; + `; this.setState({ homepageScripts }); } }) @@ -1244,6 +1245,7 @@ export class BrowserTab extends PureComponent { this.onFrameLoadStarted(url); break; } + case 'NAV_CHANGE': { const { url, title } = data.payload; this.setState({ @@ -1257,6 +1259,7 @@ export class BrowserTab extends PureComponent { this.updateTabInfo(data.payload.url); break; } + case 'GET_TITLE_FOR_BOOKMARK': if (data.payload.title) { this.setState({ diff --git a/app/core/BackgroundBridge.js b/app/core/BackgroundBridge.js index ecccf65cec4..97d3f723b73 100644 --- a/app/core/BackgroundBridge.js +++ b/app/core/BackgroundBridge.js @@ -40,15 +40,14 @@ class Port extends EventEmitter { } export class BackgroundBridge extends EventEmitter { - constructor(webview, url, middlewares, shouldExposeAccounts, isMainFrame) { + constructor({ webview, url, getRpcMethodMiddleware, shouldExposeAccounts, isMainFrame }) { super(); - const senderUrl = new URL(url); this.url = url; - this.hostname = senderUrl.hostname; + this.hostname = new URL(url).hostname; this.isMainFrame = isMainFrame; this._webviewRef = webview && webview.current; - this.middlewares = middlewares; + this.createMiddleware = getRpcMethodMiddleware; this.shouldExposeAccounts = shouldExposeAccounts; this.provider = Engine.context.NetworkController.provider; this.blockTracker = this.provider._blockTracker; @@ -58,8 +57,8 @@ export class BackgroundBridge extends EventEmitter { // setup multiplexing const mux = setupMultiplex(portStream); // connect features - this.setupProviderConnection(mux.createStream('provider'), senderUrl); - this.setupPublicConfig(mux.createStream('publicConfig'), senderUrl); + this.setupProviderConnection(mux.createStream('provider')); + this.setupPublicConfig(mux.createStream('publicConfig')); Engine.context.NetworkController.subscribe(this.sendStateUpdate); Engine.context.PreferencesController.subscribe(this.sendStateUpdate); @@ -80,10 +79,9 @@ export class BackgroundBridge extends EventEmitter { /** * A method for serving our ethereum provider over a given stream. * @param {*} outStream - The stream to provide over. - * @param {URL} senderUrl - The URI of the requesting resource. */ - setupProviderConnection(outStream, senderUrl) { - const engine = this.setupProviderEngine(senderUrl); + setupProviderConnection(outStream) { + const engine = this.setupProviderEngine(); // setup connection const providerStream = createEngineStream({ engine }); @@ -102,8 +100,8 @@ export class BackgroundBridge extends EventEmitter { /** * A method for creating a provider that is safely restricted for the requesting domain. **/ - setupProviderEngine(senderUrl) { - const origin = senderUrl.hostname; + setupProviderEngine() { + const origin = this.hostname; // setup json rpc engine stack const engine = new RpcEngine(); const provider = this.provider; @@ -125,26 +123,12 @@ export class BackgroundBridge extends EventEmitter { engine.push(subscriptionManager.middleware); // watch asset - // requestAccounts - engine.push(this.middlewares.eth_requestAccounts(senderUrl)); - engine.push(this.middlewares.eth_accounts(senderUrl)); - // Signing methods - engine.push(this.middlewares.eth_sign()); - engine.push(this.middlewares.personal_sign()); - engine.push(this.middlewares.eth_signTypedData()); - engine.push(this.middlewares.eth_signTypedData_v3()); - engine.push(this.middlewares.eth_signTypedData_v4()); - - // walletMethods - engine.push(this.middlewares.web3_clientVersion()); - engine.push(this.middlewares.wallet_scanQRCode()); - engine.push(this.middlewares.wallet_watchAsset()); - - // Mobile specific methods - engine.push(this.middlewares.wallet_watchAsset()); - engine.push(this.middlewares.metamask_removeFavorite()); - engine.push(this.middlewares.metamask_showTutorial()); - engine.push(this.middlewares.metamask_showAutocomplete()); + // user-facing RPC methods + engine.push( + this.createMiddleware({ + hostname: this.hostname + }) + ); // forward to metamask primary provider engine.push(providerAsMiddleware(provider)); @@ -160,12 +144,11 @@ export class BackgroundBridge extends EventEmitter { * this is a good candidate for deprecation. * * @param {*} outStream - The stream to provide public config over. - * @param {URL} senderUrl - The URL of requesting resource */ - setupPublicConfig(outStream, senderUrl) { + setupPublicConfig(outStream) { const configStore = this.createPublicConfigStore({ // check the providerApprovalController's approvedOrigins - checkIsEnabled: () => this.shouldExposeAccounts(senderUrl.hostname) + checkIsEnabled: () => this.shouldExposeAccounts(this.hostname) }); const configStream = asStream(configStore); diff --git a/app/util/middlewares.js b/app/util/middlewares.js index b6c37ab0c90..df871587b1f 100644 --- a/app/util/middlewares.js +++ b/app/util/middlewares.js @@ -8,6 +8,13 @@ import Logger from './Logger'; export function createOriginMiddleware(opts) { return function originMiddleware(/** @type {any} */ req, /** @type {any} */ _, /** @type {Function} */ next) { req.origin = opts.origin; + + // web3-provider-engine compatibility + // TODO:provider delete this after web3-provider-engine deprecation + if (!req.params) { + req.params = []; + } + next(); }; } diff --git a/package.json b/package.json index a2ff628cf46..c131c1fbe06 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "dnode": "1.2.2", "eth-contract-metadata": "1.11.0", "eth-ens-namehash": "2.0.8", - "eth-json-rpc-errors": "2.0.0", + "eth-json-rpc-errors": "^2.0.1", "eth-json-rpc-filters": "4.1.1", "eth-json-rpc-infura": "4.0.0", "eth-json-rpc-middleware": "4.3.0", @@ -179,7 +179,7 @@ "jest": "24.5.0", "jest-serializer": "24.4.0", "lint-staged": "8.1.5", - "metamask-mobile-provider": "git+ssh://git@github.com/metamask/metamask-mobile-provider.git#ed038a21d4301ece822f3926e504a1081ae65456", + "metamask-mobile-provider": "^1.0.1", "metro": "0.55.0", "metro-react-native-babel-preset": "0.53.0", "octonode": "0.9.5", diff --git a/yarn.lock b/yarn.lock index eef94d64817..5c042d0b86d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3715,17 +3715,24 @@ eth-hd-keyring@^3.4.0: events "^1.1.1" xtend "^4.0.1" -eth-json-rpc-errors@2.0.0, eth-json-rpc-errors@^2.0.0: +eth-json-rpc-errors@^1.0.1, eth-json-rpc-errors@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/eth-json-rpc-errors/-/eth-json-rpc-errors-1.1.1.tgz#148377ef55155585981c21ff574a8937f9d6991f" + integrity sha512-WT5shJ5KfNqHi9jOZD+ID8I1kuYWNrigtZat7GOQkvwo99f8SzAVaEcWhJUv656WiZOAg3P1RiJQANtUmDmbIg== + dependencies: + fast-safe-stringify "^2.0.6" + +eth-json-rpc-errors@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/eth-json-rpc-errors/-/eth-json-rpc-errors-2.0.0.tgz#bdc19df8b80a820844709193372f0d75fb74fed8" integrity sha512-casdSTVOxbC3ptfUdclJRvU0Sgmdm/QtezLku8l4iVR5wNFe+KF+tfnlm2I84xxpx7mkyyHeeUxmRkcB5Os6mw== dependencies: fast-safe-stringify "^2.0.6" -eth-json-rpc-errors@^1.0.1, eth-json-rpc-errors@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/eth-json-rpc-errors/-/eth-json-rpc-errors-1.1.1.tgz#148377ef55155585981c21ff574a8937f9d6991f" - integrity sha512-WT5shJ5KfNqHi9jOZD+ID8I1kuYWNrigtZat7GOQkvwo99f8SzAVaEcWhJUv656WiZOAg3P1RiJQANtUmDmbIg== +eth-json-rpc-errors@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/eth-json-rpc-errors/-/eth-json-rpc-errors-2.0.1.tgz#e7a4c4e3c76913dff26dbc021966c72b2822e0f2" + integrity sha512-ldF9fdzkdHAgTWmqh/bgHi7uH+8icAyjcEdFOBGD32zTWd7J66VDo0rBaiaQPowXitiyAcs1R23Mje1gkdIofA== dependencies: fast-safe-stringify "^2.0.6" @@ -7102,9 +7109,10 @@ merkle-patricia-tree@^2.1.2, merkle-patricia-tree@^2.3.2: rlp "^2.0.0" semaphore ">=1.0.1" -"metamask-mobile-provider@git+ssh://git@github.com/metamask/metamask-mobile-provider.git#ed038a21d4301ece822f3926e504a1081ae65456": - version "1.0.0" - resolved "git+ssh://git@github.com/metamask/metamask-mobile-provider.git#ed038a21d4301ece822f3926e504a1081ae65456" +metamask-mobile-provider@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/metamask-mobile-provider/-/metamask-mobile-provider-1.0.1.tgz#d8387f29f907c0ea48f1e344b0a034fb28dc4d01" + integrity sha512-64NIPJhnswFV+tCQN8XURs2zxQWBFMcp/RgVLpWioncc2Ahj0AyR5E6+gHKT/jS1PjR/gs18p83C8keRqPNtCg== methods@^1.1.1: version "1.1.2"