diff --git a/CHANGELOG.md b/CHANGELOG.md index 69ca119ba..ed70624e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,32 @@ ## Current Develop Branch +## 6.4.0 Wed Apr 17 2019 + +- [#6445](https://github.com/MetaMask/metamask-extension/pull/6445): * Move send to pages/ +- [#6470](https://github.com/MetaMask/metamask-extension/pull/6470): update publishing.md with dev diagram +- [#6403](https://github.com/MetaMask/metamask-extension/pull/6403): Update to eth-method-registry@1.2.0 +- [#6468](https://github.com/MetaMask/metamask-extension/pull/6468): Fix switcher height when Custom RPC is selected or loading +- [#6459](https://github.com/MetaMask/metamask-extension/pull/6459): feature: add Goerli support +- [#6444](https://github.com/MetaMask/metamask-extension/pull/6444): Fixes #6321 & #6421 - Add Localhost 8545 for network dropdown names +- [#6454](https://github.com/MetaMask/metamask-extension/pull/6454): Bump eth-contract-metadata +- [#6448](https://github.com/MetaMask/metamask-extension/pull/6448): Remove unneeded array cloning in getSendToAccounts selector +- [#6056](https://github.com/MetaMask/metamask-extension/pull/6056): repeated getSelectedAddress() func send.selectors.js removed +- [#6422](https://github.com/MetaMask/metamask-extension/pull/6422): Added Chrome limited site access solution doc +- [#6424](https://github.com/MetaMask/metamask-extension/pull/6424): feature: switch token pricing to CoinGecko API +- [#6428](https://github.com/MetaMask/metamask-extension/pull/6428): Don't inject web3 on sharefile.com +- [#6417](https://github.com/MetaMask/metamask-extension/pull/6417): Metrics updates +- [#6420](https://github.com/MetaMask/metamask-extension/pull/6420): Fix links to MetamaskInpageProvider in porting_to_new_environment.md +- [#6362](https://github.com/MetaMask/metamask-extension/pull/6362): Remove broken image walkthrough from metamaskbot comment +- [#6401](https://github.com/MetaMask/metamask-extension/pull/6401): metamask-controller - use improved provider-as-middleware utility +- [#6406](https://github.com/MetaMask/metamask-extension/pull/6406): remove user actions controller +- [#6399](https://github.com/MetaMask/metamask-extension/pull/6399): doc - publishing - typo fix +- [#6396](https://github.com/MetaMask/metamask-extension/pull/6396): pin eth-contract-metadata to last commit hash +- [#6397](https://github.com/MetaMask/metamask-extension/pull/6397): Change coinbase to wyre +- [#6395](https://github.com/MetaMask/metamask-extension/pull/6395): bump ledger and trezor keyring +- [#6389](https://github.com/MetaMask/metamask-extension/pull/6389): Fix display of gas chart on Ethereum networks +- [#6382](https://github.com/MetaMask/metamask-extension/pull/6382): Remove NoticeController + ## 6.3.2 Mon Apr 8 2019 - [#6389](https://github.com/MetaMask/metamask-extension/pull/6389): Fix display of gas chart on ethereum networks @@ -21,7 +47,7 @@ - [#6302](https://github.com/MetaMask/metamask-extension/pull/6302): Replaces the coinbase link in the deposit modal with one for wyre - [#6307](https://github.com/MetaMask/metamask-extension/pull/6307): Centre the notification in the current window - [#6312](https://github.com/MetaMask/metamask-extension/pull/6312): Fixes popups not showing when screen size is odd -- [#6326](https://github.com/MetaMask/metamask-extension/pull/6326): Fix oversized loading overlay on gas customization modal. +- [#6326](https://github.com/MetaMask/metamask-extension/pull/6326): Fix oversized loading overlay on gas customization modal. - [#6330](https://github.com/MetaMask/metamask-extension/pull/6330): Stop reloading dapps on network change allowing dapps to decide if it should refresh or not - [#6332](https://github.com/MetaMask/metamask-extension/pull/6332): Enable mobile sync - [#6333](https://github.com/MetaMask/metamask-extension/pull/6333): Redesign of the settings screen diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 154925d1a..184507cbb 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -139,6 +139,9 @@ "approved": { "message": "Approved" }, + "asset": { + "message": "Asset" + }, "attemptingConnect": { "message": "Attempting to connect to blockchain." }, @@ -308,6 +311,12 @@ "connectingToRinkeby": { "message": "Connecting to Rinkeby Test Network" }, + "connectingToLocalhost": { + "message": "Connecting to Localhost 8545" + }, + "connectingToGoerli": { + "message": "Connecting to Goerli Test Network" + }, "connectingToUnknown": { "message": "Connecting to Unknown Network" }, @@ -1226,6 +1235,9 @@ "ropsten": { "message": "Ropsten Test Network" }, + "goerli": { + "message": "Goerli Test Network" + }, "rpc": { "message": "Custom RPC" }, @@ -1342,6 +1354,9 @@ "selectAnAccountHelp": { "message": "Select the account to view in MetaMask" }, + "selectAnAsset": { + "message": "Select an Asset" + }, "selectAHigherGasFee": { "message": "Select a higher gas fee to accelerate the processing of your transaction.*" }, diff --git a/app/manifest.json b/app/manifest.json index 5e464dc2f..3dc815c51 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "6.3.2", + "version": "6.4.0", "manifest_version": 2, "author": "https://metamask.io", "description": "__MSG_appDescription__", diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 68b6117e5..2325cecdd 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -275,6 +275,7 @@ function blacklistedDomainCheck () { 'harbourair.com', 'ani.gamer.com.tw', 'blueskybooking.com', + 'sharefile.com', ] const currentUrl = window.location.href let currentRegex diff --git a/app/scripts/controllers/network/createInfuraClient.js b/app/scripts/controllers/network/createInfuraClient.js index 884b94db3..70b332867 100644 --- a/app/scripts/controllers/network/createInfuraClient.js +++ b/app/scripts/controllers/network/createInfuraClient.js @@ -49,6 +49,10 @@ function createNetworkAndChainIdMiddleware ({ network }) { netId = '42' chainId = '0x2a' break + case 'goerli': + netId = '5' + chainId = '0x05' + break default: throw new Error(`createInfuraClient - unknown network "${network}"`) } diff --git a/app/scripts/controllers/network/enums.js b/app/scripts/controllers/network/enums.js index 3190eb37c..2f7025392 100644 --- a/app/scripts/controllers/network/enums.js +++ b/app/scripts/controllers/network/enums.js @@ -3,16 +3,19 @@ const RINKEBY = 'rinkeby' const KOVAN = 'kovan' const MAINNET = 'mainnet' const LOCALHOST = 'localhost' +const GOERLI = 'goerli' const MAINNET_CODE = 1 const ROPSTEN_CODE = 3 const RINKEYBY_CODE = 4 const KOVAN_CODE = 42 +const GOERLI_CODE = 5 const ROPSTEN_DISPLAY_NAME = 'Ropsten' const RINKEBY_DISPLAY_NAME = 'Rinkeby' const KOVAN_DISPLAY_NAME = 'Kovan' const MAINNET_DISPLAY_NAME = 'Main Ethereum Network' +const GOERLI_DISPLAY_NAME = 'Goerli' module.exports = { ROPSTEN, @@ -20,12 +23,15 @@ module.exports = { KOVAN, MAINNET, LOCALHOST, + GOERLI, MAINNET_CODE, ROPSTEN_CODE, RINKEYBY_CODE, KOVAN_CODE, + GOERLI_CODE, ROPSTEN_DISPLAY_NAME, RINKEBY_DISPLAY_NAME, KOVAN_DISPLAY_NAME, MAINNET_DISPLAY_NAME, + GOERLI_DISPLAY_NAME, } diff --git a/app/scripts/controllers/network/network.js b/app/scripts/controllers/network/network.js index 47432c1e2..c00ac7e6a 100644 --- a/app/scripts/controllers/network/network.js +++ b/app/scripts/controllers/network/network.js @@ -20,8 +20,9 @@ const { KOVAN, MAINNET, LOCALHOST, + GOERLI, } = require('./enums') -const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET] +const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET, GOERLI] const env = process.env.METAMASK_ENV const METAMASK_DEBUG = process.env.METAMASK_DEBUG @@ -140,10 +141,10 @@ module.exports = class NetworkController extends EventEmitter { this.providerConfig = providerConfig } - async setProviderType (type) { + async setProviderType (type, rpcTarget = '', ticker = 'ETH', nickname = '') { assert.notEqual(type, 'rpc', `NetworkController - cannot call "setProviderType" with type 'rpc'. use "setRpcTarget"`) assert(INFURA_PROVIDER_TYPES.includes(type) || type === LOCALHOST, `NetworkController - Unknown rpc type "${type}"`) - const providerConfig = { type } + const providerConfig = { type, rpcTarget, ticker, nickname } this.providerConfig = providerConfig } diff --git a/app/scripts/controllers/network/util.js b/app/scripts/controllers/network/util.js index 261dae721..b0afccd7e 100644 --- a/app/scripts/controllers/network/util.js +++ b/app/scripts/controllers/network/util.js @@ -3,13 +3,16 @@ const { RINKEBY, KOVAN, MAINNET, + GOERLI, ROPSTEN_CODE, RINKEYBY_CODE, KOVAN_CODE, + GOERLI_CODE, ROPSTEN_DISPLAY_NAME, RINKEBY_DISPLAY_NAME, KOVAN_DISPLAY_NAME, MAINNET_DISPLAY_NAME, + GOERLI_DISPLAY_NAME, } = require('./enums') const networkToNameMap = { @@ -17,9 +20,11 @@ const networkToNameMap = { [RINKEBY]: RINKEBY_DISPLAY_NAME, [KOVAN]: KOVAN_DISPLAY_NAME, [MAINNET]: MAINNET_DISPLAY_NAME, + [GOERLI]: GOERLI_DISPLAY_NAME, [ROPSTEN_CODE]: ROPSTEN_DISPLAY_NAME, [RINKEYBY_CODE]: RINKEBY_DISPLAY_NAME, [KOVAN_CODE]: KOVAN_DISPLAY_NAME, + [GOERLI_CODE]: GOERLI_DISPLAY_NAME, } const getNetworkDisplayName = key => networkToNameMap[key] diff --git a/app/scripts/controllers/recent-blocks.js b/app/scripts/controllers/recent-blocks.js index 982ad2aa4..a2b5d1bae 100644 --- a/app/scripts/controllers/recent-blocks.js +++ b/app/scripts/controllers/recent-blocks.js @@ -8,8 +8,9 @@ const { RINKEBY, KOVAN, MAINNET, + GOERLI, } = require('./network/enums') -const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET] +const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET, GOERLI] class RecentBlocksController { diff --git a/app/scripts/controllers/token-rates.js b/app/scripts/controllers/token-rates.js index 867d36433..4e396bb59 100644 --- a/app/scripts/controllers/token-rates.js +++ b/app/scripts/controllers/token-rates.js @@ -28,16 +28,16 @@ class TokenRatesController { async updateExchangeRates () { if (!this.isActive) { return } const contractExchangeRates = {} - const nativeCurrency = this.currency ? this.currency.getState().nativeCurrency.toUpperCase() : 'ETH' - const pairs = this._tokens.map(token => `pairs[]=${token.address}/${nativeCurrency}`) - const query = pairs.join('&') + const nativeCurrency = this.currency ? this.currency.getState().nativeCurrency.toLowerCase() : 'eth' + const pairs = this._tokens.map(token => token.address).join(',') + const query = `contract_addresses=${pairs}&vs_currencies=${nativeCurrency}` if (this._tokens.length > 0) { try { - const response = await fetch(`https://exchanges.balanc3.net/pie?${query}&autoConversion=false`) - const { prices = [] } = await response.json() - prices.forEach(({ pair, price }) => { - const address = pair.split('/')[0] - contractExchangeRates[normalizeAddress(address)] = typeof price === 'number' ? price : 0 + const response = await fetch(`https://api.coingecko.com/api/v3/simple/token_price/ethereum?${query}`) + const prices = await response.json() + this._tokens.forEach(token => { + const price = prices[token.address.toLowerCase()] + contractExchangeRates[normalizeAddress(token.address)] = price ? price[nativeCurrency] : 0 }) } catch (error) { log.warn(`MetaMask - TokenRatesController exchange rate fetch failed.`, error) diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 68394d1ae..71cfb875c 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -218,6 +218,12 @@ inpageProvider.publicConfigStore.subscribe(function (state) { web3.eth.defaultAccount = state.selectedAddress }) +inpageProvider.publicConfigStore.subscribe(function (state) { + if (state.onboardingcomplete) { + window.postMessage('onboardingcomplete', '*') + } +}) + // need to make sure we aren't affected by overlapping namespaces // and that we dont affect the app with our namespace // mostly a fix for web3's BigNumber if AMD's "define" is defined... diff --git a/app/scripts/lib/buy-eth-url.js b/app/scripts/lib/buy-eth-url.js index fbe6c6c9e..5cae83a9f 100644 --- a/app/scripts/lib/buy-eth-url.js +++ b/app/scripts/lib/buy-eth-url.js @@ -28,6 +28,8 @@ function getBuyEthUrl ({ network, amount, address, service }) { return 'https://www.rinkeby.io/' case 'kovan-faucet': return 'https://github.com/kovan-testnet/faucet' + case 'goerli-faucet': + return 'https://goerli-faucet.slock.it/' } throw new Error(`Unknown cryptocurrency exchange or faucet: "${service}"`) } @@ -42,6 +44,8 @@ function getDefaultServiceForNetwork (network) { return 'rinkeby-faucet' case '42': return 'kovan-faucet' + case '5': + return 'goerli-faucet' } throw new Error(`No default cryptocurrency exchange or faucet for networkId: "${network}"`) } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index d2a55acaa..0506e3116 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -320,6 +320,7 @@ module.exports = class MetamaskController extends EventEmitter { const result = { selectedAddress: memState.isUnlocked ? memState.selectedAddress : undefined, networkVersion: memState.network, + onboardingcomplete: memState.completedOnboarding, } return result } diff --git a/development/states/navigate-txs.json b/development/states/navigate-txs.json index 23255abed..584a754f0 100644 --- a/development/states/navigate-txs.json +++ b/development/states/navigate-txs.json @@ -230,7 +230,8 @@ "kovan": "ok", "mainnet": "ok", "rinkeby": "ok", - "ropsten": "ok" + "ropsten": "ok", + "goerli": "ok" }, "lostAccounts": [] }, @@ -320,4 +321,4 @@ "toSmartContract": false, "fetchingData": false } -} \ No newline at end of file +} diff --git a/development/states/pending-tx.json b/development/states/pending-tx.json index b4a6565fa..28c1751bd 100644 --- a/development/states/pending-tx.json +++ b/development/states/pending-tx.json @@ -704,7 +704,8 @@ "mainnet": "ok", "ropsten": "ok", "kovan": "ok", - "rinkeby": "ok" + "rinkeby": "ok", + "goerli": "ok" }, "shapeShiftTxList": [], "lostAccounts": [] @@ -735,4 +736,4 @@ "os": "mac" }, "browser": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36" -} \ No newline at end of file +} diff --git a/docs/limited_site_access.md b/docs/limited_site_access.md new file mode 100644 index 000000000..f703d5c7e --- /dev/null +++ b/docs/limited_site_access.md @@ -0,0 +1,5 @@ +# Google Chrome/Brave Limited Site Access for Extensions + +Problem: MetaMask doesn't work with limited site access enabled under Chrome's extensions. + +Solution: In addition to the site you wish to whitelist, you must add 'api.infura.io' as another domain, so the MetaMask extension is authorized to make RPC calls to Infura. diff --git a/docs/porting_to_new_environment.md b/docs/porting_to_new_environment.md index d901f2b78..f7a2ac8b7 100644 --- a/docs/porting_to_new_environment.md +++ b/docs/porting_to_new_environment.md @@ -10,7 +10,7 @@ The `metamask-background` describes the file at `app/scripts/background.js`, whi When a new site is visited, the WebExtension creates a new `ContentScript` in that page's context, which can be seen at `app/scripts/contentscript.js`. This script represents a per-page setup process, which creates the per-page `web3` api, connects it to the background script via the Port API (wrapped in a [stream abstraction](https://github.com/substack/stream-handbook)), and injected into the DOM before anything loads. -The most confusing part about porting MetaMask to a new platform is the way we provide the Web3 API over a series of streams between contexts. Once you understand how we create the [InpageProvider](../app/scripts/lib/inpage-provider.js) in the [inpage.js script](../app/scripts/inpage.js), you will be able to understand how the [port-stream](../app/scripts/lib/port-stream.js) is just a thin wrapper around the [postMessage API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage), and a similar stream API can be wrapped around any communication channel to communicate with the `MetaMaskController` via its `setupUntrustedCommunication(stream, domain)` method. +The most confusing part about porting MetaMask to a new platform is the way we provide the Web3 API over a series of streams between contexts. Once you understand how we create the [MetamaskInpageProvider](https://github.com/MetaMask/metamask-inpage-provider/blob/master/index.js) in the [inpage.js script](../app/scripts/inpage.js), you will be able to understand how the [port-stream](../app/scripts/lib/port-stream.js) is just a thin wrapper around the [postMessage API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage), and a similar stream API can be wrapped around any communication channel to communicate with the `MetaMaskController` via its `setupUntrustedCommunication(stream, domain)` method. ### The MetaMask Controller @@ -89,7 +89,7 @@ MetaMask has two kinds of [duplex stream APIs](https://github.com/substack/strea If you are making a MetaMask-powered browser for a new platform, one of the trickiest tasks will be injecting the Web3 API into websites that are visited. On WebExtensions, we actually have to pipe data through a total of three JS contexts just to let sites talk to our background process (site -> contentscript -> background). -To see how we do that, you can refer to the [inpage script](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/inpage.js) that we inject into every website. There you can see it creates a multiplex stream to the background, and uses it to initialize what we call the [inpage-provider](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/lib/inpage-provider.js), which you can see stubs a few methods out, but mostly just passes calls to `sendAsync` through the stream it's passed! That's really all the magic that's needed to create a web3-like API in a remote context, once you have a stream to MetaMask available. +To see how we do that, you can refer to the [inpage script](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/inpage.js) that we inject into every website. There you can see it creates a multiplex stream to the background, and uses it to initialize what we call the [MetamaskInpageProvider](https://github.com/MetaMask/metamask-inpage-provider/blob/master/index.js), which you can see stubs a few methods out, but mostly just passes calls to `sendAsync` through the stream it's passed! That's really all the magic that's needed to create a web3-like API in a remote context, once you have a stream to MetaMask available. In `inpage.js` you can see we create a `PortStream`, that's just a class we use to wrap WebExtension ports as streams, so we can reuse our favorite stream abstraction over the more irregular API surface of the WebExtension. In a new platform, you will probably need to construct this stream differently. The key is that you need to construct a stream that talks from the site context to the background. Once you have that set up, it works like magic! diff --git a/docs/publishing.md b/docs/publishing.md index 1668afe1e..392e20955 100644 --- a/docs/publishing.md +++ b/docs/publishing.md @@ -2,6 +2,13 @@ When publishing a new version of MetaMask, we follow this procedure: +## Overview + +The below diagram outlines our process for design, development, and release. Building MetaMask is a community affair, and many steps of the process invite participation from external contributors as indicated. All QA, code review, and release of new versions is done by members of the core MetaMask team. + +mm-dev-process + + ## Preparation We try to ensure certain criteria are met before deploying: diff --git a/package-lock.json b/package-lock.json index 11eab5718..902dbe004 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9759,8 +9759,8 @@ } }, "eth-contract-metadata": { - "version": "github:MetaMask/eth-contract-metadata#92e7d1442c7585bfd24e50a0fda78df11dedadfe", - "from": "github:MetaMask/eth-contract-metadata#92e7d1442c7585bfd24e50a0fda78df11dedadfe" + "version": "github:MetaMask/eth-contract-metadata#41a14e8004bdd37eaba5af5f2bb1fc4f4ff7063f", + "from": "github:MetaMask/eth-contract-metadata#41a14e8004bdd37eaba5af5f2bb1fc4f4ff7063f" }, "eth-ens-namehash": { "version": "2.0.8", @@ -9806,6 +9806,31 @@ "requires": { "bn.js": "^4.11.8", "ethereumjs-util": "^6.0.0" + }, + "dependencies": { + "ethereumjs-util": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.1.0.tgz", + "integrity": "sha512-URESKMFbDeJxnAxPppnk2fN6Y3BIatn9fwn76Lm8bQlt+s52TpG8dN9M66MLPuRAiAOIqL3dfwqWJf0sd0fL0Q==", + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "ethjs-util": "0.1.6", + "keccak": "^1.0.2", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1", + "secp256k1": "^3.0.1" + } + } + } + }, + "ethjs-util": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz", + "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==", + "requires": { + "is-hex-prefixed": "1.0.0", + "strip-hex-prefix": "1.0.0" } } } @@ -9814,8 +9839,7 @@ "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799", "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git", "requires": { - "bn.js": "^4.11.8", - "ethereumjs-util": "^6.0.0" + "bn.js": "^4.11.8" } }, "ethereumjs-util": { @@ -9896,6 +9920,31 @@ "requires": { "bn.js": "^4.11.8", "ethereumjs-util": "^6.0.0" + }, + "dependencies": { + "ethereumjs-util": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.1.0.tgz", + "integrity": "sha512-URESKMFbDeJxnAxPppnk2fN6Y3BIatn9fwn76Lm8bQlt+s52TpG8dN9M66MLPuRAiAOIqL3dfwqWJf0sd0fL0Q==", + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "ethjs-util": "0.1.6", + "keccak": "^1.0.2", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1", + "secp256k1": "^3.0.1" + } + } + } + }, + "ethjs-util": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz", + "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==", + "requires": { + "is-hex-prefixed": "1.0.0", + "strip-hex-prefix": "1.0.0" } } } @@ -9904,8 +9953,7 @@ "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215", "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git", "requires": { - "bn.js": "^4.11.8", - "ethereumjs-util": "^6.0.0" + "bn.js": "^4.11.8" } }, "ethereumjs-util": { @@ -10076,6 +10124,33 @@ "requires": { "bn.js": "^4.11.8", "ethereumjs-util": "^6.0.0" + }, + "dependencies": { + "ethereumjs-util": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.1.0.tgz", + "integrity": "sha512-URESKMFbDeJxnAxPppnk2fN6Y3BIatn9fwn76Lm8bQlt+s52TpG8dN9M66MLPuRAiAOIqL3dfwqWJf0sd0fL0Q==", + "dev": true, + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "ethjs-util": "0.1.6", + "keccak": "^1.0.2", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1", + "secp256k1": "^3.0.1" + } + } + } + }, + "ethjs-util": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz", + "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==", + "dev": true, + "requires": { + "is-hex-prefixed": "1.0.0", + "strip-hex-prefix": "1.0.0" } } } @@ -10084,14 +10159,14 @@ "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799", "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git", "requires": { - "bn.js": "^4.11.8", - "ethereumjs-util": "^6.0.0" + "bn.js": "^4.11.8" } }, "ethereumjs-util": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", + "dev": true, "requires": { "bn.js": "^4.11.0", "create-hash": "^1.1.2", @@ -10192,8 +10267,7 @@ "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215", "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git", "requires": { - "bn.js": "^4.11.8", - "ethereumjs-util": "^6.0.0" + "bn.js": "^4.11.8" } }, "ethereumjs-util": { @@ -10324,9 +10398,9 @@ } }, "eth-method-registry": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/eth-method-registry/-/eth-method-registry-1.0.0.tgz", - "integrity": "sha1-8Ij3Wdad6f3BK3EEm83GiKMoOLY=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/eth-method-registry/-/eth-method-registry-1.2.0.tgz", + "integrity": "sha512-m+nphH4kOxz5KTvQ+BeIKVggxAul1sp4Ev09lfxRXIEHM1t/6NQEtaErL5ddTDFXXFVtTiW8uC9edTVUTnBZNg==", "requires": { "ethjs": "^0.3.0" }, @@ -10502,8 +10576,7 @@ "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215", "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git", "requires": { - "bn.js": "^4.11.8", - "ethereumjs-util": "^6.0.0" + "bn.js": "^4.11.8" } }, "ethereumjs-util": { diff --git a/package.json b/package.json index 884c77387..2a02954d2 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,8 @@ "test:integration:build": "gulp build:scss", "test:e2e:drizzle:beta": "SELENIUM_BROWSER=chrome test/e2e/beta/run-drizzle.sh", "test:e2e:chrome": "SELENIUM_BROWSER=chrome test/e2e/beta/run-all.sh", + "test:web3:chrome": "SELENIUM_BROWSER=chrome test/e2e/beta/run-web3.sh", + "test:web3:firefox": "SELENIUM_BROWSER=firefox test/e2e/beta/run-web3.sh", "test:e2e:firefox": "SELENIUM_BROWSER=firefox test/e2e/beta/run-all.sh", "test:screens": "shell-parallel -s 'npm run ganache:start' -x 'sleep 3 && npm run test:screens:run'", "test:screens:run": "node test/screens/new-ui.js", @@ -82,14 +84,14 @@ "ensnare": "^1.0.0", "eth-bin-to-ops": "^1.0.1", "eth-block-tracker": "^4.1.0", - "eth-contract-metadata": "github:MetaMask/eth-contract-metadata#92e7d1442c7585bfd24e50a0fda78df11dedadfe", + "eth-contract-metadata": "github:MetaMask/eth-contract-metadata#41a14e8004bdd37eaba5af5f2bb1fc4f4ff7063f", "eth-ens-namehash": "^2.0.8", "eth-hd-keyring": "^1.2.2", "eth-json-rpc-filters": "^3.0.1", "eth-json-rpc-infura": "^3.0.0", "eth-keyring-controller": "^3.3.1", "eth-ledger-bridge-keyring": "^0.2.0", - "eth-method-registry": "^1.0.0", + "eth-method-registry": "^1.2.0", "eth-phishing-detect": "^1.1.4", "eth-query": "^2.1.2", "eth-sig-util": "^2.0.2", diff --git a/test/data/v17-long-history.json b/test/data/v17-long-history.json index a33d425f8..cccb1ef96 100644 --- a/test/data/v17-long-history.json +++ b/test/data/v17-long-history.json @@ -54,7 +54,8 @@ "mainnet": "degraded", "ropsten": "ok", "kovan": "ok", - "rinkeby": "ok" + "rinkeby": "ok", + "goerli": "ok" } }, "BlacklistController": { @@ -3050,4 +3051,4 @@ ] } } -} \ No newline at end of file +} diff --git a/test/e2e/beta/metamask-beta-responsive-ui.spec.js b/test/e2e/beta/metamask-beta-responsive-ui.spec.js index b1ed8536e..9ef560556 100644 --- a/test/e2e/beta/metamask-beta-responsive-ui.spec.js +++ b/test/e2e/beta/metamask-beta-responsive-ui.spec.js @@ -123,7 +123,7 @@ describe('MetaMask', function () { }) it('clicks the "I agree" option on the metametrics opt-in screen', async () => { - const optOutButton = await findElement(driver, By.css('.btn-confirm')) + const optOutButton = await findElement(driver, By.css('.btn-primary')) optOutButton.click() await delay(largeDelayMs) }) diff --git a/test/e2e/beta/run-web3.sh b/test/e2e/beta/run-web3.sh new file mode 100755 index 000000000..9f77060de --- /dev/null +++ b/test/e2e/beta/run-web3.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +export PATH="$PATH:./node_modules/.bin" + +shell-parallel -s 'static-server test/web3 --port 8080' -x 'sleep 5 && mocha test/e2e/beta/web3.spec' \ No newline at end of file diff --git a/test/e2e/beta/web3.spec.js b/test/e2e/beta/web3.spec.js new file mode 100644 index 000000000..b3962c821 --- /dev/null +++ b/test/e2e/beta/web3.spec.js @@ -0,0 +1,365 @@ +const path = require('path') +const assert = require('assert') +const webdriver = require('selenium-webdriver') +const { By } = webdriver +const { + delay, + buildChromeWebDriver, + buildFirefoxWebdriver, + installWebExt, + getExtensionIdChrome, + getExtensionIdFirefox, +} = require('../func') +const { + checkBrowserForConsoleErrors, + closeAllWindowHandlesExcept, + findElement, + findElements, + openNewPage, + switchToWindowWithTitle, + verboseReportOnFailure, + waitUntilXWindowHandles, +} = require('./helpers') +const fetchMockResponses = require('./fetch-mocks.js') + + +describe('Using MetaMask with an existing account', function () { + let extensionId + let driver + + const testSeedPhrase = 'forum vessel pink push lonely enact gentle tail admit parrot grunt dress' + const regularDelayMs = 1000 + const largeDelayMs = regularDelayMs * 2 + + const button = async (x) => { + const buttoncheck = x + await buttoncheck.click() + await delay(largeDelayMs) + const [results] = await findElements(driver, By.css('#results')) + const resulttext = await results.getText() + var parsedData = JSON.parse(resulttext) + + return (parsedData) + + } + + this.timeout(0) + this.bail(true) + + before(async function () { + let extensionUrl + switch (process.env.SELENIUM_BROWSER) { + case 'chrome': { + const extensionPath = path.resolve('dist/chrome') + driver = buildChromeWebDriver(extensionPath) + extensionId = await getExtensionIdChrome(driver) + await delay(regularDelayMs) + extensionUrl = `chrome-extension://${extensionId}/home.html` + break + } + case 'firefox': { + const extensionPath = path.resolve('dist/firefox') + driver = buildFirefoxWebdriver() + await installWebExt(driver, extensionPath) + await delay(regularDelayMs) + extensionId = await getExtensionIdFirefox(driver) + extensionUrl = `moz-extension://${extensionId}/home.html` + break + } + } + // Depending on the state of the application built into the above directory (extPath) and the value of + // METAMASK_DEBUG we will see different post-install behaviour and possibly some extra windows. Here we + // are closing any extraneous windows to reset us to a single window before continuing. + const [tab1] = await driver.getAllWindowHandles() + await closeAllWindowHandlesExcept(driver, [tab1]) + await driver.switchTo().window(tab1) + await driver.get(extensionUrl) + }) + + beforeEach(async function () { + await driver.executeScript( + 'window.origFetch = window.fetch.bind(window);' + + 'window.fetch = ' + + '(...args) => { ' + + 'if (args[0] === "https://ethgasstation.info/json/ethgasAPI.json") { return ' + + 'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.ethGasBasic + '\')) }); } else if ' + + '(args[0] === "https://ethgasstation.info/json/predictTable.json") { return ' + + 'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.ethGasPredictTable + '\')) }); } else if ' + + '(args[0].match(/chromeextensionmm/)) { return ' + + 'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.metametrics + '\')) }); } else if ' + + '(args[0] === "https://dev.blockscale.net/api/gasexpress.json") { return ' + + 'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.gasExpress + '\')) }); } ' + + 'return window.origFetch(...args); };' + + 'function cancelInfuraRequest(requestDetails) {' + + 'console.log("Canceling: " + requestDetails.url);' + + 'return {' + + 'cancel: true' + + '};' + + ' }' + + 'window.chrome && window.chrome.webRequest && window.chrome.webRequest.onBeforeRequest.addListener(' + + 'cancelInfuraRequest,' + + '{urls: ["https://*.infura.io/*"]},' + + '["blocking"]' + + ');' + ) + }) + + afterEach(async function () { + if (process.env.SELENIUM_BROWSER === 'chrome') { + const errors = await checkBrowserForConsoleErrors(driver) + if (errors.length) { + const errorReports = errors.map(err => err.message) + const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` + console.error(new Error(errorMessage)) + } + } + if (this.currentTest.state === 'failed') { + await verboseReportOnFailure(driver, this.currentTest) + } + }) + + after(async function () { + await driver.quit() + }) + + describe('First time flow starting from an existing seed phrase', () => { + it('clicks the continue button on the welcome screen', async () => { + await findElement(driver, By.css('.welcome-page__header')) + const welcomeScreenBtn = await findElement(driver, By.css('.first-time-flow__button')) + welcomeScreenBtn.click() + await delay(largeDelayMs) + }) + + it('clicks the "Import Wallet" option', async () => { + const customRpcButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Import Wallet')]`)) + customRpcButton.click() + await delay(largeDelayMs) + }) + + it('clicks the "No thanks" option on the metametrics opt-in screen', async () => { + const optOutButton = await findElement(driver, By.css('.btn-default')) + optOutButton.click() + await delay(largeDelayMs) + }) + + it('imports a seed phrase', async () => { + const [seedTextArea] = await findElements(driver, By.css('textarea.first-time-flow__textarea')) + await seedTextArea.sendKeys(testSeedPhrase) + await delay(regularDelayMs) + + const [password] = await findElements(driver, By.id('password')) + await password.sendKeys('correct horse battery staple') + const [confirmPassword] = await findElements(driver, By.id('confirm-password')) + confirmPassword.sendKeys('correct horse battery staple') + + const tosCheckBox = await findElement(driver, By.css('.first-time-flow__checkbox')) + await tosCheckBox.click() + + const [importButton] = await findElements(driver, By.xpath(`//button[contains(text(), 'Import')]`)) + await importButton.click() + await delay(regularDelayMs) + }) + + it('clicks through the success screen', async () => { + await findElement(driver, By.xpath(`//div[contains(text(), 'Congratulations')]`)) + const doneButton = await findElement(driver, By.css('button.first-time-flow__button')) + await doneButton.click() + await delay(regularDelayMs) + }) + }) + + + describe('opens dapp', () => { + + it('switches to mainnet', async () => { + const networkDropdown = await findElement(driver, By.css('.network-name')) + await networkDropdown.click() + await delay(regularDelayMs) + + const [mainnet] = await findElements(driver, By.xpath(`//span[contains(text(), 'Main Ethereum Network')]`)) + await mainnet.click() + await delay(largeDelayMs * 2) + }) + + it('', async () => { + await openNewPage(driver, 'http://127.0.0.1:8080/') + await delay(regularDelayMs) + + await waitUntilXWindowHandles(driver, 3) + const windowHandles = await driver.getAllWindowHandles() + + const extension = windowHandles[0] + const popup = await switchToWindowWithTitle(driver, 'MetaMask Notification', windowHandles) + const dapp = windowHandles.find(handle => handle !== extension && handle !== popup) + + await delay(regularDelayMs) + const approveButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) + await approveButton.click() + + await driver.switchTo().window(dapp) + await delay(regularDelayMs) + + + }) + }) + + describe('testing web3 methods', async () => { + + + it('testing hexa methods', async () => { + + + var List = await driver.findElements(By.className('hexaNumberMethods')) + + for (let i = 0; i < List.length; i++) { + try { + + var parsedData = await button(List[i]) + console.log(parsedData) + var result = parseInt(parsedData.result, 16) + + assert.equal((typeof result === 'number'), true) + await delay(regularDelayMs) + } catch (err) { + console.log(err) + assert(false) + + } + } + }) + + it('testing booleanMethods', async () => { + + var List = await driver.findElements(By.className('booleanMethods')) + + for (let i = 0; i < List.length; i++) { + try { + + var parsedData = await button(List[i]) + console.log(parsedData) + var result = parsedData.result + + assert.equal(result, false) + await delay(regularDelayMs) + } catch (err) { + console.log(err) + assert(false) + + + } + } + + }) + + it('testing transactionMethods', async () => { + + var List = await driver.findElements(By.className('transactionMethods')) + + for (let i = 0; i < List.length; i++) { + try { + + var parsedData = await button(List[i]) + + console.log(parsedData.result.blockHash) + + var result = [] + result.push(parseInt(parsedData.result.blockHash, 16)) + result.push(parseInt(parsedData.result.blockNumber, 16)) + result.push(parseInt(parsedData.result.gas, 16)) + result.push(parseInt(parsedData.result.gasPrice, 16)) + result.push(parseInt(parsedData.result.hash, 16)) + result.push(parseInt(parsedData.result.input, 16)) + result.push(parseInt(parsedData.result.nonce, 16)) + result.push(parseInt(parsedData.result.r, 16)) + result.push(parseInt(parsedData.result.s, 16)) + result.push(parseInt(parsedData.result.v, 16)) + result.push(parseInt(parsedData.result.to, 16)) + result.push(parseInt(parsedData.result.value, 16)) + + + result.forEach((value) => { + assert.equal((typeof value === 'number'), true) + }) + + + } catch (err) { + + console.log(err) + assert(false) + + + } + } + + }) + + it('testing blockMethods', async () => { + + var List = await driver.findElements(By.className('blockMethods')) + + for (let i = 0; i < List.length; i++) { + try { + + var parsedData = await button(List[i]) + console.log(JSON.stringify(parsedData) + i) + + console.log(parsedData.result.parentHash) + + var result = parseInt(parsedData.result.parentHash, 16) + + assert.equal((typeof result === 'number'), true) + await delay(regularDelayMs) + } catch (err) { + + console.log(err) + assert(false) + + + } + } + }) + + it('testing methods', async () => { + + var List = await driver.findElements(By.className('methods')) + var parsedData + var result + + for (let i = 0; i < List.length; i++) { + try { + + if (i === 2) { + + parsedData = await button(List[i]) + console.log(parsedData.result.blockHash) + + result = parseInt(parsedData.result.blockHash, 16) + + assert.equal((typeof result === 'number' || (result === 0)), true) + await delay(regularDelayMs) + } else { + parsedData = await button(List[i]) + console.log(parsedData.result) + + result = parseInt(parsedData.result, 16) + + assert.equal((typeof result === 'number' || (result === 0)), true) + await delay(regularDelayMs) + } + + + } catch (err) { + + console.log(err) + assert(false) + + + } + } + }) + + + }) + + + }) diff --git a/test/integration/lib/confirm-sig-requests.js b/test/integration/lib/confirm-sig-requests.js index e4540c4f2..c3b0dfcff 100644 --- a/test/integration/lib/confirm-sig-requests.js +++ b/test/integration/lib/confirm-sig-requests.js @@ -44,7 +44,7 @@ async function runConfirmSigRequestsTest (assert, done) { let confirmSigRowValue = await queryAsync($, '.request-signature__row-value') assert.equal(confirmSigRowValue[0].textContent, '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0') - let confirmSigSignButton = await queryAsync($, 'button.btn-primary.btn--large') + let confirmSigSignButton = await queryAsync($, 'button.btn-secondary.btn--large') confirmSigSignButton[0].click() await timeout(1000) confirmSigHeadline = await queryAsync($, '.request-signature__headline') @@ -53,7 +53,7 @@ async function runConfirmSigRequestsTest (assert, done) { confirmSigRowValue = await queryAsync($, '.request-signature__row-value') assert.ok(confirmSigRowValue[0].textContent.match(/^#\sTerms\sof\sUse/)) - confirmSigSignButton = await queryAsync($, 'button.btn-primary.btn--large') + confirmSigSignButton = await queryAsync($, 'button.btn-secondary.btn--large') confirmSigSignButton[0].click() await timeout(1000) confirmSigHeadline = await queryAsync($, '.request-signature__headline') @@ -63,7 +63,7 @@ async function runConfirmSigRequestsTest (assert, done) { assert.equal(confirmSigRowValue[0].textContent, 'Hi, Alice!') assert.equal(confirmSigRowValue[1].textContent, '1337') - confirmSigSignButton = await queryAsync($, 'button.btn-primary.btn--large') + confirmSigSignButton = await queryAsync($, 'button.btn-secondary.btn--large') confirmSigSignButton[0].click() await timeout(2000) diff --git a/test/integration/lib/send-new-ui.js b/test/integration/lib/send-new-ui.js index d7003f4cc..6a58611d1 100644 --- a/test/integration/lib/send-new-ui.js +++ b/test/integration/lib/send-new-ui.js @@ -43,16 +43,13 @@ async function runSendFlowTest (assert, done) { selectState.val('send new ui') reactTriggerChange(selectState[0]) - const sendScreenButton = await queryAsync($, 'button.btn-primary.transaction-view-balance__button') + const sendScreenButton = await queryAsync($, 'button.btn-secondary.transaction-view-balance__button') assert.ok(sendScreenButton[1], 'send screen button present') sendScreenButton[1].click() const sendTitle = await queryAsync($, '.page-container__title') assert.equal(sendTitle[0].textContent, 'Send ETH', 'Send screen title is correct') - const sendCopy = await queryAsync($, '.page-container__subtitle') - assert.equal(sendCopy[0].textContent, 'Only send ETH to an Ethereum address.', 'Send screen has copy') - const sendFromField = await queryAsync($, '.send-v2__form-field') assert.ok(sendFromField[0], 'send screen has a from field') @@ -72,7 +69,7 @@ async function runSendFlowTest (assert, done) { const sendToAccountAddress = sendToFieldInput.val() assert.equal(sendToAccountAddress, '0x2f8D4a878cFA04A6E60D46362f5644DeAb66572D', 'send to dropdown selects the correct address') - const sendAmountField = await queryAsync($, '.send-v2__form-row:eq(2)') + const sendAmountField = await queryAsync($, '.send-v2__form-row:eq(3)') sendAmountField.find('.unit-input')[0].click() const sendAmountFieldInput = await findAsync(sendAmountField, '.unit-input__input') @@ -88,7 +85,7 @@ async function runSendFlowTest (assert, done) { errorMessage = $('.send-v2__error') assert.equal(errorMessage.length, 0, 'send should stop rendering amount error message after amount is corrected') - const sendButton = await queryAsync($, 'button.btn-primary.btn--large.page-container__footer-button') + const sendButton = await queryAsync($, 'button.btn-secondary.btn--large.page-container__footer-button') assert.equal(sendButton[0].textContent, 'Next', 'next button rendered') sendButton[0].click() await timeout() @@ -115,14 +112,14 @@ async function runSendFlowTest (assert, done) { sendToFieldInputInEdit[0].focus() sendToFieldInputInEdit.val('0xd85a4b6a394794842887b8284293d69163007bbb') - const sendAmountFieldInEdit = await queryAsync($, '.send-v2__form-row:eq(2)') + const sendAmountFieldInEdit = await queryAsync($, '.send-v2__form-row:eq(3)') sendAmountFieldInEdit.find('.unit-input')[0].click() const sendAmountFieldInputInEdit = sendAmountFieldInEdit.find('.unit-input__input') sendAmountFieldInputInEdit.val('1.0') reactTriggerChange(sendAmountFieldInputInEdit[0]) - const sendButtonInEdit = await queryAsync($, '.btn-primary.btn--large.page-container__footer-button') + const sendButtonInEdit = await queryAsync($, '.btn-secondary.btn--large.page-container__footer-button') assert.equal(sendButtonInEdit[0].textContent, 'Next', 'next button in edit rendered') selectState.val('send new ui') diff --git a/test/unit/app/controllers/infura-controller-test.js b/test/unit/app/controllers/infura-controller-test.js index 7bd95dd4b..316f80378 100644 --- a/test/unit/app/controllers/infura-controller-test.js +++ b/test/unit/app/controllers/infura-controller-test.js @@ -4,7 +4,7 @@ const InfuraController = require('../../../../app/scripts/controllers/infura') describe('infura-controller', function () { let infuraController, sandbox, networkStatus - const response = {'mainnet': 'degraded', 'ropsten': 'ok', 'kovan': 'ok', 'rinkeby': 'down'} + const response = {'mainnet': 'degraded', 'ropsten': 'ok', 'kovan': 'ok', 'rinkeby': 'down', 'goerli': 'ok'} before(async function () { infuraController = new InfuraController() @@ -58,5 +58,15 @@ describe('infura-controller', function () { assert.equal(networkStatus.rinkeby, 'down') }) }) + + describe('Goerli', function () { + it('should have Goerli', function () { + assert.equal(Object.keys(networkStatus)[4], 'goerli') + }) + + it('should have a value for Goerli status', function () { + assert.equal(networkStatus.goerli, 'ok') + }) + }) }) }) diff --git a/test/unit/app/controllers/network-contoller-test.js b/test/unit/app/controllers/network-contoller-test.js index 7959e6cc1..32f7b337d 100644 --- a/test/unit/app/controllers/network-contoller-test.js +++ b/test/unit/app/controllers/network-contoller-test.js @@ -92,6 +92,9 @@ describe('Network utils', () => { }, { input: 'mainnet', expected: 'Main Ethereum Network', + }, { + input: 'goerli', + expected: 'Goerli', }, ] diff --git a/test/unit/app/controllers/transactions/recipient-blacklist-checker-test.js b/test/unit/app/controllers/transactions/recipient-blacklist-checker-test.js index cb413545f..d3e47c67e 100644 --- a/test/unit/app/controllers/transactions/recipient-blacklist-checker-test.js +++ b/test/unit/app/controllers/transactions/recipient-blacklist-checker-test.js @@ -4,6 +4,7 @@ const { ROPSTEN_CODE, RINKEYBY_CODE, KOVAN_CODE, + GOERLI_CODE, } = require('../../../../../app/scripts/controllers/network/enums') const KeyringController = require('eth-keyring-controller') @@ -27,14 +28,14 @@ describe('Recipient Blacklist Checker', function () { describe('#checkAccount', function () { it('does not fail on test networks', function () { let callCount = 0 - const networks = [ROPSTEN_CODE, RINKEYBY_CODE, KOVAN_CODE] + const networks = [ROPSTEN_CODE, RINKEYBY_CODE, KOVAN_CODE, GOERLI_CODE] for (const networkId in networks) { publicAccounts.forEach((account) => { recipientBlackListChecker.checkAccount(networkId, account) callCount++ }) } - assert.equal(callCount, 30) + assert.equal(callCount, 40) }) it('fails on mainnet', function () { diff --git a/test/unit/ui/etherscan-prefix-for-network.spec.js b/test/unit/ui/etherscan-prefix-for-network.spec.js index f0aeb8306..45e6e563d 100644 --- a/test/unit/ui/etherscan-prefix-for-network.spec.js +++ b/test/unit/ui/etherscan-prefix-for-network.spec.js @@ -23,4 +23,8 @@ describe('Etherscan Network Prefix', () => { assert.equal(etherscanNetworkPrefix(42), 'kovan.') }) + it('returs goerli as prefix for networkId of 5', () => { + assert.equal(etherscanNetworkPrefix(5), 'goerli.') + }) + }) diff --git a/test/web3/index.html b/test/web3/index.html new file mode 100644 index 000000000..cbc43290c --- /dev/null +++ b/test/web3/index.html @@ -0,0 +1,105 @@ + + + Web3 Test Dapp + + +
+
hexaNumberMethods
+
+ + + + + + + +
+
+ + + +
+
+ + + + + + +
+
+
+
booleanMethods
+
+ + + +
+
+
+
transactionMethods
+
+ + + + + +
+
+ +
+
blockMethods
+ +
+ + + + +
+
+ + + +
+
+ +
+
Methods
+
+ + + + +
+
+
+
+
+ + + + + + + + + + diff --git a/test/web3/schema.js b/test/web3/schema.js new file mode 100644 index 000000000..61977f140 --- /dev/null +++ b/test/web3/schema.js @@ -0,0 +1,209 @@ +/* eslint no-unused-vars: 0 */ + +var params = { + // diffrent params used in the methods + param: [], + blockHashParams: '0xb3b20624f8f0f86eb50dd04688409e5cea4bd02d700bf6e79e9384d47d6a5a35', + filterParams: ['0xfe704947a3cd3ca12541458a4321c869'], + transactionHashParams: [ + '0xbb3a336e3f823ec18197f1e13ee875700f08f03e2cab75f0d0b118dabb44cba0', + ], + blockHashAndIndexParams: [ + '0xb3b20624f8f0f86eb50dd04688409e5cea4bd02d700bf6e79e9384d47d6a5a35', + '0x0', + ], + uncleByBlockNumberAndIndexParams: ['0x29c', '0x0'], + blockParameterParams: '0x5bad55', + data: '0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675', + addressParams: '0xc94770007dda54cF92009BFF0dE90c06F603a09f', + getStorageAtParams: [ + '0x295a70b2de5e3953354a6a8344e616ed314d7251', + '0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9', + '0x65a8db', + ], + getCodeParams: ['0x06012c8cf97bead5deae237070f9587f8e7a266d', '0x65a8db'], + estimateTransaction: { + from: '0xb60e8dd61c5d32be8058bb8eb970870f07233155', + to: '0xd46e8dd67c5d32be8058bb8eb970870f07244567', + gas: '0x76c0', + gasPrice: '0x9184e72a000', + value: '0x9184e72a', + data: '0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675', + }, + filterGetLogs: [{'blockHash': '0x7c5a35e9cb3e8ae0e221ab470abae9d446c3a5626ce6689fc777dcffcab52c70', 'topics': ['0x241ea03ca20251805084d27d4440371c34a0b85ff108f6bb5611248f73818b80']}], + block: { + __required: [], + number: 'Q', + hash: 'D32', + parentHash: 'D32', + nonce: 'D', + sha3Uncles: 'D', + logsBloom: 'D', + transactionsRoot: 'D', + stateRoot: 'D', + receiptsRoot: 'D', + miner: 'D', + difficulty: 'Q', + totalDifficulty: 'Q', + extraData: 'D', + size: 'Q', + gasLimit: 'Q', + gasUsed: 'Q', + timestamp: 'Q', + transactions: ['DATA|Transaction'], + uncles: ['D'], + }, + transaction: { + __required: [], + hash: 'D32', + nonce: 'Q', + blockHash: 'D32', + blockNumber: 'Q', + transactionIndex: 'Q', + from: 'D20', + to: 'D20', + value: 'Q', + gasPrice: 'Q', + gas: 'Q', + input: 'D', + }, + receipt: { + __required: [], + transactionHash: 'D32', + transactionIndex: 'Q', + blockHash: 'D32', + blockNumber: 'Q', + cumulativeGasUsed: 'Q', + gasUsed: 'Q', + contractAddress: 'D20', + logs: ['FilterChange'], + }, + + filterChange: { + __required: [], + removed: 'B', + logIndex: 'Q', + transactionIndex: 'Q', + transactionHash: 'D32', + blockHash: 'D32', + blockNumber: 'Q', + address: 'D20', + data: 'Array|DATA', + topics: ['D'], + }, +} + +var methods = { + hexaNumberMethods: { + // these are the methods which have output in the form of hexa decimal numbers + eth_blockNumber: ['eth_blockNumber', params.param, 'Q'], + eth_gasPrice: ['eth_gasPrice', params.param, 'Q'], + eth_newBlockFilter: ['eth_newBlockFilter', params.param, 'Q'], + eth_newPendingTransactionFilter: [ + 'eth_newPendingTransactionFilter', + params.param, + 'Q', + ], + eth_getUncleCountByBlockHash: [ + 'eth_getUncleCountByBlockHash', + [params.blockHashParams], + 'Q', + 1, + ], + eth_getBlockTransactionCountByHash: [ + 'eth_getBlockTransactionCountByHash', + [params.blockHashParams], + 'Q', + 1, + ], + eth_getTransactionCount: [ + 'eth_getTransactionCount', + [params.addressParams, params.blockParameterParams], + 'Q', + 1, + 2, + ], + eth_getBalance: ['eth_getBalance', [params.addressParams, 'latest'], 'Q', 1, 2], + eth_estimateGas: ['eth_estimateGas', [params.estimateTransaction], 'Q', 1], + eth_getUncleCountByBlockNumber: [ + 'eth_getUncleCountByBlockNumber', + [params.blockParameterParams], + 'Q', + 1, + ], + eth_getBlockTransactionCountByNumber: [ + 'eth_getBlockTransactionCountByNumber', + ['latest'], + 'Q', + 1, + ], + eth_protocolVersion: ['eth_protocolVersion', params.param, 'S'], + eth_getCode: ['eth_getCode', params.getCodeParams, 'D', 1, 2], + }, + booleanMethods: { + // these are the methods which have output in the form of boolean + eth_uninstallFilter: ['eth_uninstallFilter', params.filterParams, 'B', 1], + eth_mining: ['eth_mining', params.param, 'B'], + eth_syncing: ['eth_syncing', params.param, 'B|EthSyncing'], + }, + transactionMethods: { + // these are the methods which have output in the form of transaction object + eth_getTransactionByHash: [ + 'eth_getTransactionByHash', + params.transactionHashParams, + params.transaction, + 1, + ], + eth_getTransactionByBlockHashAndIndex: [ + 'eth_getTransactionByBlockHashAndIndex', + params.blockHashAndIndexParams, + params.transaction, + 2, + ], + eth_getTransactionByBlockNumberAndIndex: [ + 'eth_getTransactionByBlockNumberAndIndex', + [params.blockParameterParams, '0x0'], + params.transaction, + 2, + ], + + }, + blockMethods: { + // these are the methods which have output in the form of a block + + eth_getUncleByBlockNumberAndIndex: [ + 'eth_getUncleByBlockNumberAndIndex', + params.uncleByBlockNumberAndIndexParams, + params.block, + 2, + ], + eth_getBlockByHash: [ + 'eth_getBlockByHash', + [params.params, false], + params.block, + 2, + ], + eth_getBlockByNumber: [ + 'eth_getBlockByNumber', + [params.blockParameterParams, false], + params.block, + 2, + ], + }, + + methods: { + // these are the methods which have output in the form of bytes data + + eth_call: ['eth_call', [params.estimateTransaction, 'latest'], 'D', 1, 2], + eth_getStorageAt: ['eth_getStorageAt', params.getStorageAtParams, 'D', 2, 2], + eth_getTransactionReceipt: [ + 'eth_getTransactionReceipt', + params.transactionHashParams, + params.receipt, + 1, + ], + + }, + +} + diff --git a/test/web3/web3.js b/test/web3/web3.js new file mode 100644 index 000000000..5c2de078d --- /dev/null +++ b/test/web3/web3.js @@ -0,0 +1,34 @@ +/* eslint no-undef: 0 */ + +var json = methods + +web3.currentProvider.enable().then(() => { + + Object.keys(json).forEach(methodGroupKey => { + + console.log(methodGroupKey) + const methodGroup = json[methodGroupKey] + console.log(methodGroup) + Object.keys(methodGroup).forEach(methodKey => { + + const methodButton = document.getElementById(methodKey) + methodButton.addEventListener('click', function (event) { + + window.ethereum.sendAsync({ + method: methodKey, + params: methodGroup[methodKey][1], + }, function (err, result) { + if (err) { + console.log(err) + console.log(methodKey) + } else { + document.getElementById('results').innerHTML = JSON.stringify(result) + } + }) + }) + + }) + + }) + }) + diff --git a/ui/app/components/app/add-token-button/index.scss b/ui/app/components/app/add-token-button/index.scss index 39f404716..c4350a2d3 100644 --- a/ui/app/components/app/add-token-button/index.scss +++ b/ui/app/components/app/add-token-button/index.scss @@ -17,10 +17,7 @@ } &__button { - font-size: 0.75rem; + @extend %small-link; margin: 1rem; - text-transform: uppercase; - color: $curious-blue; - cursor: pointer; } } diff --git a/ui/app/components/app/coinbase-form.js b/ui/app/components/app/coinbase-form.js deleted file mode 100644 index 24d287604..000000000 --- a/ui/app/components/app/coinbase-form.js +++ /dev/null @@ -1,69 +0,0 @@ -const Component = require('react').Component -const PropTypes = require('prop-types') -const h = require('react-hyperscript') -const inherits = require('util').inherits -const connect = require('react-redux').connect -const actions = require('../../store/actions') - -CoinbaseForm.contextTypes = { - t: PropTypes.func, -} - -module.exports = connect(mapStateToProps)(CoinbaseForm) - - -function mapStateToProps (state) { - return { - warning: state.appState.warning, - } -} - -inherits(CoinbaseForm, Component) - -function CoinbaseForm () { - Component.call(this) -} - -CoinbaseForm.prototype.render = function () { - var props = this.props - - return h('.flex-column', { - style: { - marginTop: '35px', - padding: '25px', - width: '100%', - }, - }, [ - h('.flex-row', { - style: { - justifyContent: 'space-around', - margin: '33px', - marginTop: '0px', - }, - }, [ - h('button.btn-green', { - onClick: this.toCoinbase.bind(this), - }, this.context.t('continueToCoinbase')), - - h('button.btn-red', { - onClick: () => props.dispatch(actions.goHome()), - }, this.context.t('cancel')), - ]), - ]) -} - -CoinbaseForm.prototype.toCoinbase = function () { - const props = this.props - const address = props.buyView.buyAddress - props.dispatch(actions.buyEth({ network: '1', address, amount: 0 })) -} - -CoinbaseForm.prototype.renderLoading = function () { - return h('img', { - style: { - width: '27px', - marginRight: '-27px', - }, - src: 'images/loading.svg', - }) -} diff --git a/ui/app/components/app/customize-gas-modal/index.js b/ui/app/components/app/customize-gas-modal/index.js index dca77bb00..1f9436810 100644 --- a/ui/app/components/app/customize-gas-modal/index.js +++ b/ui/app/components/app/customize-gas-modal/index.js @@ -18,11 +18,11 @@ const { MIN_GAS_PRICE_DEC, MIN_GAS_LIMIT_DEC, MIN_GAS_PRICE_GWEI, -} = require('../send/send.constants') +} = require('../../../pages/send/send.constants') const { isBalanceSufficient, -} = require('../send/send.utils') +} = require('../../../pages/send/send.utils') const { conversionUtil, @@ -47,7 +47,7 @@ const { const { getGasPrice, getGasLimit, -} = require('../send/send.selectors') +} = require('../../../pages/send/send.selectors') function mapStateToProps (state) { const selectedToken = getSelectedToken(state) @@ -382,7 +382,7 @@ CustomizeGasModal.prototype.render = function () { onClick: this.props.hideModal, }, [this.context.t('cancel')]), h(Button, { - type: 'primary', + type: 'secondary', className: 'send-v2__customize-gas__save', onClick: () => !error && this.save(newGasPrice, gasLimit, gasTotal), disabled: error, diff --git a/ui/app/components/app/dropdowns/network-dropdown.js b/ui/app/components/app/dropdowns/network-dropdown.js index 3d9037a06..dbe3f1bc8 100644 --- a/ui/app/components/app/dropdowns/network-dropdown.js +++ b/ui/app/components/app/dropdowns/network-dropdown.js @@ -204,6 +204,28 @@ NetworkDropdown.prototype.render = function () { ] ), + h( + DropdownMenuItem, + { + key: 'goerli', + closeMenu: () => this.props.hideNetworkDropdown(), + onClick: () => this.handleClick('goerli'), + style: dropdownMenuItemStyle, + }, + [ + providerType === 'goerli' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'), + h(NetworkDropdownIcon, { + backgroundColor: '#3099f2', // $dodger-blue + isSelected: providerType === 'goerli', + }), + h('span.network-name-item', { + style: { + color: providerType === 'goerli' ? '#ffffff' : '#9b9b9b', + }, + }, this.context.t('goerli')), + ] + ), + h( DropdownMenuItem, { @@ -285,6 +307,10 @@ NetworkDropdown.prototype.getNetworkName = function () { name = this.context.t('kovan') } else if (providerName === 'rinkeby') { name = this.context.t('rinkeby') + } else if (providerName === 'localhost') { + name = this.context.t('localhost') + } else if (providerName === 'goerli') { + name = this.context.t('goerli') } else { name = provider.nickname || this.context.t('unknownNetwork') } diff --git a/ui/app/components/app/dropdowns/tests/network-dropdown.test.js b/ui/app/components/app/dropdowns/tests/network-dropdown.test.js index 91e7899a7..4a81b973f 100644 --- a/ui/app/components/app/dropdowns/tests/network-dropdown.test.js +++ b/ui/app/components/app/dropdowns/tests/network-dropdown.test.js @@ -62,7 +62,7 @@ describe('Network Dropdown', () => { }) it('renders 7 DropDownMenuItems ', () => { - assert.equal(wrapper.find(DropdownMenuItem).length, 7) + assert.equal(wrapper.find(DropdownMenuItem).length, 8) }) it('checks background color for first NetworkDropdownIcon', () => { @@ -82,16 +82,20 @@ describe('Network Dropdown', () => { }) it('checks background color for fifth NetworkDropdownIcon', () => { - assert.equal(wrapper.find(NetworkDropdownIcon).at(4).prop('innerBorder'), '1px solid #9b9b9b') - }) - - it('checks dropdown for frequestRPCList from state ', () => { - assert.equal(wrapper.find(DropdownMenuItem).at(5).text(), '✓http://localhost:7545') + assert.equal(wrapper.find(NetworkDropdownIcon).at(4).prop('backgroundColor'), '#3099f2') // Goerli Blue }) it('checks background color for sixth NetworkDropdownIcon', () => { assert.equal(wrapper.find(NetworkDropdownIcon).at(5).prop('innerBorder'), '1px solid #9b9b9b') }) + it('checks dropdown for frequestRPCList from state ', () => { + assert.equal(wrapper.find(DropdownMenuItem).at(6).text(), '✓http://localhost:7545') + }) + + it('checks background color for seventh NetworkDropdownIcon', () => { + assert.equal(wrapper.find(NetworkDropdownIcon).at(6).prop('innerBorder'), '1px solid #9b9b9b') + }) + }) }) diff --git a/ui/app/components/app/ens-input.js b/ui/app/components/app/ens-input.js index 274058a1b..424c5061e 100644 --- a/ui/app/components/app/ens-input.js +++ b/ui/app/components/app/ens-input.js @@ -10,7 +10,7 @@ const networkMap = require('ethjs-ens/lib/network-map.json') const ensRE = /.+\..+$/ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' const connect = require('react-redux').connect -const ToAutoComplete = require('./send/to-autocomplete').default +const ToAutoComplete = require('../../pages/send/to-autocomplete').default const log = require('loglevel') const { isValidENSAddress } = require('../../helpers/utils/util') diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js b/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js index d541056f4..0e7e30347 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js @@ -63,7 +63,7 @@ import { import { calcGasTotal, isBalanceSufficient, -} from '../../send/send.utils' +} from '../../../../pages/send/send.utils' import { addHexPrefix } from 'ethereumjs-util' import { getAdjacentGasPrices, extrapolateY } from '../gas-price-chart/gas-price-chart.utils' diff --git a/ui/app/components/app/gas-customization/gas-price-button-group/index.scss b/ui/app/components/app/gas-customization/gas-price-button-group/index.scss index cb2f3ecf1..92b4aba42 100644 --- a/ui/app/components/app/gas-customization/gas-price-button-group/index.scss +++ b/ui/app/components/app/gas-customization/gas-price-button-group/index.scss @@ -65,6 +65,7 @@ .gas-price-button-group--small { display: flex; justify-content: stretch; + height: 54px; @media screen and (max-width: $break-small) { max-width: 260px; @@ -80,10 +81,14 @@ &__label { font-weight: 500; + line-height: 16px; + padding-bottom: 4px; } &__primary-currency { font-size: 12px; + line-height: 12px; + padding-bottom: 2px; @media screen and (max-width: 575px) { font-size: 10px; @@ -92,6 +97,8 @@ &__secondary-currency { font-size: 12px; + line-height: 12px; + padding-bottom: 2px; @media screen and (max-width: 575px) { font-size: 10px; @@ -99,19 +106,13 @@ } &__loading-container { - height: 78px; + height: 54px; } .button-group__button, .button-group__button--active { - height: 78px; background: white; color: $scorpion; - padding-top: 9px; - padding-left: 8.5px; - - @media screen and (max-width: $break-small) { - padding-left: 4px; - } + padding: 0 4px; div { display: flex; diff --git a/ui/app/components/app/loading-network-screen/loading-network-screen.component.js b/ui/app/components/app/loading-network-screen/loading-network-screen.component.js index 348a997c8..97b16d08f 100644 --- a/ui/app/components/app/loading-network-screen/loading-network-screen.component.js +++ b/ui/app/components/app/loading-network-screen/loading-network-screen.component.js @@ -45,6 +45,10 @@ export default class LoadingNetworkScreen extends PureComponent { name = this.context.t('connectingToKovan') } else if (providerName === 'rinkeby') { name = this.context.t('connectingToRinkeby') + } else if (providerName === 'localhost') { + name = this.context.t('connectingToLocalhost') + } else if (providerName === 'goerli') { + name = this.context.t('connectingToGoerli') } else { name = this.context.t('connectingTo', [providerId]) } diff --git a/ui/app/components/app/modal/modal.component.js b/ui/app/components/app/modal/modal.component.js index 49e131b3c..44b180ac8 100644 --- a/ui/app/components/app/modal/modal.component.js +++ b/ui/app/components/app/modal/modal.component.js @@ -20,7 +20,7 @@ export default class Modal extends PureComponent { } static defaultProps = { - submitType: 'primary', + submitType: 'secondary', cancelType: 'default', } diff --git a/ui/app/components/app/modal/tests/modal.component.test.js b/ui/app/components/app/modal/tests/modal.component.test.js index a13d7c06a..5922177a6 100644 --- a/ui/app/components/app/modal/tests/modal.component.test.js +++ b/ui/app/components/app/modal/tests/modal.component.test.js @@ -12,7 +12,7 @@ describe('Modal Component', () => { assert.equal(wrapper.find('.modal-container').length, 1) const buttons = wrapper.find(Button) assert.equal(buttons.length, 1) - assert.equal(buttons.at(0).props().type, 'primary') + assert.equal(buttons.at(0).props().type, 'secondary') }) it('should render a modal with a cancel and a submit button', () => { @@ -38,7 +38,7 @@ describe('Modal Component', () => { cancelButton.simulate('click') assert.equal(handleCancel.callCount, 1) - assert.equal(submitButton.props().type, 'primary') + assert.equal(submitButton.props().type, 'secondary') assert.equal(submitButton.props().children, 'Submit') assert.equal(handleSubmit.callCount, 0) submitButton.simulate('click') diff --git a/ui/app/components/app/modals/account-details-modal.js b/ui/app/components/app/modals/account-details-modal.js index 94ed04df9..1b1ca6b8e 100644 --- a/ui/app/components/app/modals/account-details-modal.js +++ b/ui/app/components/app/modals/account-details-modal.js @@ -84,7 +84,7 @@ AccountDetailsModal.prototype.render = function () { h('div.account-modal-divider'), h(Button, { - type: 'primary', + type: 'secondary', className: 'account-modal__button', onClick: () => global.platform.openWindow({ url: genAccountLink(address, network) }), }, this.context.t('etherscanView')), @@ -92,7 +92,7 @@ AccountDetailsModal.prototype.render = function () { // Holding on redesign for Export Private Key functionality exportPrivateKeyFeatureEnabled ? h(Button, { - type: 'primary', + type: 'secondary', className: 'account-modal__button', onClick: () => showExportPrivateKeyModal(), }, this.context.t('exportPrivateKey')) : null, diff --git a/ui/app/components/app/modals/customize-gas/customize-gas.component.js b/ui/app/components/app/modals/customize-gas/customize-gas.component.js index 5db5c79e7..387da2f79 100644 --- a/ui/app/components/app/modals/customize-gas/customize-gas.component.js +++ b/ui/app/components/app/modals/customize-gas/customize-gas.component.js @@ -2,7 +2,7 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import BigNumber from 'bignumber.js' import GasModalCard from '../../customize-gas-modal/gas-modal-card' -import { MIN_GAS_PRICE_GWEI } from '../../send/send.constants' +import { MIN_GAS_PRICE_GWEI } from '../../../../pages/send/send.constants' import Button from '../../../ui/button' import { @@ -128,7 +128,7 @@ export default class CustomizeGas extends Component { { t('cancel') } ) - .add('secondary', () => + .add('Button - Secondary', () => ) - .add('default', () => ( + .add('Button - Default', () => - )) - .add('large primary', () => ( + ) + .add('Button - Warning', () => - )) - .add('large secondary', () => ( + ) + .add('Button - Danger', () => - )) - .add('large default', () => ( + ) + .add('Button - Danger Primary', () => - )) + ) + .add('Button - Link', () => + + ) diff --git a/ui/app/components/ui/button/buttons.scss b/ui/app/components/ui/button/buttons.scss new file mode 100644 index 000000000..0fc87415b --- /dev/null +++ b/ui/app/components/ui/button/buttons.scss @@ -0,0 +1,244 @@ +/* + Buttons + */ + +$hover-secondary: #B0D7F2; +$hover-default: #B3B3B3; +$hover-confirm: #0372C3; +$hover-red: #FEB6BF; +$hover-red-primary: #C72837; +$hover-orange: #FFD3B5; + +%button { + @include h6; + + font-weight: 500; + font-family: Roboto, Arial; + line-height: 1.25rem; + padding: .75rem 1rem; + display: flex; + justify-content: center; + align-items: center; + box-sizing: border-box; + border-radius: 6px; + width: 100%; + outline: none; + transition: border-color .3s ease, background-color .3s ease; + + &--disabled, + &[disabled] { + cursor: auto; + opacity: .5; + pointer-events: none; + } +} + +%link { + @include h4; + + color: $Blue-500; + line-height: 1.25rem; + cursor: pointer; + background-color: transparent; + + &:hover { + color: $Blue-400; + } + + &:active { + color: $Blue-600; + } + + &--disabled, + &[disabled] { + cursor: auto; + opacity: 1; + pointer-events: none; + color: $hover-secondary; + } +} + +%small-link { + @extend %link; + @include h6; +} + +.button { + @extend %button; +} + +.btn-secondary { + color: $Blue-500; + border: 2px solid $hover-secondary; + + &:hover { + border-color: $Blue-500; + } + + &:active { + background: $Blue-000; + border-color: $Blue-500; + } + + &--disabled, + &[disabled] { + opacity: 1; + color: $hover-secondary; + } +} + +.btn-warning { + color: $Orange-500; + border: 2px solid $hover-orange; + + &:hover { + border-color: $Orange-500; + } + + &:active { + background: $Orange-000; + border-color: $Orange-500; + } + + &--disabled, + &[disabled] { + opacity: 1; + color: $hover-orange; + } +} + +.btn-danger { + color: $Red-500; + border: 2px solid $hover-red; + + &:hover { + border-color: $Red-500; + } + + &:active { + background: $Red-000; + border-color: $Red-500; + } + + &--disabled, + &[disabled] { + opacity: 1; + color: $hover-red; + } +} + +.btn-danger-primary { + color: $white; + border: 2px solid $Red-500; + background-color: $Red-500; + + &:hover { + border-color: $hover-red-primary; + background-color: $hover-red-primary; + } + + &:active { + background: $Red-600; + border-color: $Red-600; + } + + &--disabled, + &[disabled] { + opacity: 1; + border-color: $hover-red; + background-color: $hover-red; + } +} + +.btn-default { + color: $Grey-500; + border: 2px solid $hover-default; + + &:hover { + border-color: $Grey-500; + } + + &:active { + background: #FBFBFC; + border-color: $Grey-500; + } + + &--disabled, + &[disabled] { + opacity: 1; + color: $hover-default; + } +} + +.btn-primary { + color: $white; + border: 2px solid $Blue-500; + background-color: $Blue-500; + + &:hover { + border-color: $hover-confirm; + background-color: $hover-confirm; + } + + &:active { + background: $Blue-600; + border-color: $Blue-600; + } + + &--disabled, + &[disabled] { + border-color: $hover-secondary; + background-color: $hover-secondary; + } +} + +.btn-link { + @extend %link; +} + +.btn--large { + min-height: 54px; +} + +/** + All Buttons styles are deviations from design guide + */ + +.btn-raised { + color: $curious-blue; + background-color: $white; + box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.08); + padding: 6px; + height: initial; + min-height: initial; + width: initial; + min-width: initial; +} + +.btn--first-time { + height: 54px; + width: 198px; + box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .14); + color: $white; + font-size: 1.25rem; + font-weight: 500; + transition: 200ms ease-in-out; + background-color: rgba(247, 134, 28, .9); + border-radius: 0; +} + +button[disabled], +input[type="submit"][disabled] { + cursor: not-allowed; + opacity: .5; +} + +button.primary { + padding: 8px 12px; + background: #f7861c; + box-shadow: 0 3px 6px rgba(247, 134, 28, .36); + color: $white; + font-size: 1.1em; + font-family: Roboto; + text-transform: uppercase; +} diff --git a/ui/app/components/ui/page-container/index.scss b/ui/app/components/ui/page-container/index.scss index b71a3cb9d..003c5a0e2 100644 --- a/ui/app/components/ui/page-container/index.scss +++ b/ui/app/components/ui/page-container/index.scss @@ -55,11 +55,6 @@ border-top: 1px solid $geyser; flex: 0 0 auto; - .btn-default, - .btn-confirm { - font-size: 1rem; - } - header { display: flex; flex-flow: row; @@ -86,9 +81,6 @@ } &__footer-button { - height: 55px; - font-size: 1rem; - text-transform: uppercase; margin-right: 16px; &:last-of-type { diff --git a/ui/app/components/ui/page-container/page-container-footer/page-container-footer.component.js b/ui/app/components/ui/page-container/page-container-footer/page-container-footer.component.js index 85b16cefe..4ef203521 100644 --- a/ui/app/components/ui/page-container/page-container-footer/page-container-footer.component.js +++ b/ui/app/components/ui/page-container/page-container-footer/page-container-footer.component.js @@ -45,7 +45,7 @@ export default class PageContainerFooter extends Component { }