From 4963ed65c0ee6f827fa6302079d4d8e0a4fdb0aa Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Fri, 5 Apr 2019 00:46:25 -0230 Subject: [PATCH 01/29] Track seed phrase validation errors with MetaMetrics --- .../import-with-seed-phrase.component.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js b/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js index 433dad6e2..96ff11eaf 100644 --- a/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js +++ b/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js @@ -36,6 +36,20 @@ export default class ImportWithSeedPhrase extends PureComponent { .join(' ') } + componentWillMount () { + window.onbeforeunload = () => this.context.metricsEvent({ + eventOpts: { + category: 'Onboarding', + action: 'Import Seed Phrase', + name: 'Close window on import screen', + }, + customVariables: { + errorLabel: 'Seed Phrase Error', + errorMessage: this.state.seedPhraseError, + }, + }) + } + handleSeedPhraseChange (seedPhrase) { let seedPhraseError = '' @@ -172,6 +186,10 @@ export default class ImportWithSeedPhrase extends PureComponent { action: 'Import Seed Phrase', name: 'Go Back from Onboarding Import', }, + customVariables: { + errorLabel: 'Seed Phrase Error', + errorMessage: seedPhraseError, + }, }) this.props.history.push(INITIALIZE_SELECT_ACTION_ROUTE) }} From 5454266d7c5f49c3cce59a673674371bb5ccfb7f Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Fri, 5 Apr 2019 01:32:47 -0230 Subject: [PATCH 02/29] Metrics tracking gas changed - slow, average, fast, custom - on edit screen.- --- .../app/send/send-footer/send-footer.component.js | 5 +++++ .../app/send/send-footer/send-footer.container.js | 12 ++++++++++++ .../send-footer/tests/send-footer-container.test.js | 5 +++++ ui/app/helpers/utils/metametrics.util.js | 2 ++ 4 files changed, 24 insertions(+) diff --git a/ui/app/components/app/send/send-footer/send-footer.component.js b/ui/app/components/app/send/send-footer/send-footer.component.js index cc891a9b3..7d894391f 100644 --- a/ui/app/components/app/send/send-footer/send-footer.component.js +++ b/ui/app/components/app/send/send-footer/send-footer.component.js @@ -27,6 +27,7 @@ export default class SendFooter extends Component { unapprovedTxs: PropTypes.object, update: PropTypes.func, sendErrors: PropTypes.object, + gasChangedLabel: PropTypes.string, } static contextTypes = { @@ -57,6 +58,7 @@ export default class SendFooter extends Component { update, toAccounts, history, + gasChangedLabel, } = this.props const { metricsEvent } = this.context @@ -91,6 +93,9 @@ export default class SendFooter extends Component { action: 'Edit Screen', name: 'Complete', }, + customVariables: { + gasChanged: gasChangedLabel, + }, }) history.push(CONFIRM_TRANSACTION_ROUTE) }) diff --git a/ui/app/components/app/send/send-footer/send-footer.container.js b/ui/app/components/app/send/send-footer/send-footer.container.js index ea3fd7ee4..4757f6bec 100644 --- a/ui/app/components/app/send/send-footer/send-footer.container.js +++ b/ui/app/components/app/send/send-footer/send-footer.container.js @@ -31,10 +31,21 @@ import { constructTxParams, constructUpdatedTx, } from './send-footer.utils' +import { + getRenderableEstimateDataForSmallButtonsFromGWEI, + getDefaultActiveButtonIndex, +} from '../../../../selectors/custom-gas' export default connect(mapStateToProps, mapDispatchToProps)(SendFooter) function mapStateToProps (state) { + const gasButtonInfo = getRenderableEstimateDataForSmallButtonsFromGWEI(state) + const gasPrice = getGasPrice(state) + const activeButtonIndex = getDefaultActiveButtonIndex(gasButtonInfo, gasPrice) + const gasChangedLabel = activeButtonIndex >= 0 + ? gasButtonInfo[activeButtonIndex].labelKey + : 'custom' + return { amount: getSendAmount(state), data: getSendHexData(state), @@ -50,6 +61,7 @@ function mapStateToProps (state) { tokenBalance: getTokenBalance(state), unapprovedTxs: getUnapprovedTxs(state), sendErrors: getSendErrors(state), + gasChangedLabel, } } diff --git a/ui/app/components/app/send/send-footer/tests/send-footer-container.test.js b/ui/app/components/app/send/send-footer/tests/send-footer-container.test.js index 878b0aa19..64c6451f2 100644 --- a/ui/app/components/app/send/send-footer/tests/send-footer-container.test.js +++ b/ui/app/components/app/send/send-footer/tests/send-footer-container.test.js @@ -46,6 +46,10 @@ proxyquire('../send-footer.container.js', { }, './send-footer.selectors': { isSendFormInError: (s) => `mockInError:${s}` }, './send-footer.utils': utilsStubs, + '../../../../selectors/custom-gas': { + getRenderableEstimateDataForSmallButtonsFromGWEI: (s) => ([{ labelKey: `mockLabel:${s}` }]), + getDefaultActiveButtonIndex: () => 0, + }, }) describe('send-footer container', () => { @@ -68,6 +72,7 @@ describe('send-footer container', () => { tokenBalance: 'mockTokenBalance:mockState', unapprovedTxs: 'mockUnapprovedTxs:mockState', sendErrors: 'mockSendErrors:mockState', + gasChangedLabel: 'mockLabel:mockState', }) }) diff --git a/ui/app/helpers/utils/metametrics.util.js b/ui/app/helpers/utils/metametrics.util.js index 01984bd5e..5ae3e8937 100644 --- a/ui/app/helpers/utils/metametrics.util.js +++ b/ui/app/helpers/utils/metametrics.util.js @@ -23,6 +23,7 @@ const METAMETRICS_CUSTOM_ERROR_FIELD = 'errorField' const METAMETRICS_CUSTOM_ERROR_MESSAGE = 'errorMessage' const METAMETRICS_CUSTOM_RPC_NETWORK_ID = 'networkId' const METAMETRICS_CUSTOM_RPC_CHAIN_ID = 'chainId' +const METAMETRICS_CUSTOM_GAS_CHANGED = 'gasChanged' const METAMETRICS_CUSTOM_NETWORK = 'network' const METAMETRICS_CUSTOM_ENVIRONMENT_TYPE = 'environmentType' @@ -43,6 +44,7 @@ const customVariableNameIdMap = { [METAMETRICS_CUSTOM_RPC_CHAIN_ID]: 2, [METAMETRICS_CUSTOM_ERROR_FIELD]: 1, [METAMETRICS_CUSTOM_ERROR_MESSAGE]: 2, + [METAMETRICS_CUSTOM_GAS_CHANGED]: 1, } const customDimensionsNameIdMap = { From 5d948360c06c250dcf7261c8624f792a8c21fbae Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 8 Apr 2019 10:52:26 -0230 Subject: [PATCH 03/29] Distinguish between token and eth selected in home screen send button metrics event. --- .../transaction-view-balance.component.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/components/app/transaction-view-balance/transaction-view-balance.component.js b/ui/app/components/app/transaction-view-balance/transaction-view-balance.component.js index 8559e2233..fa63b6fd3 100644 --- a/ui/app/components/app/transaction-view-balance/transaction-view-balance.component.js +++ b/ui/app/components/app/transaction-view-balance/transaction-view-balance.component.js @@ -112,7 +112,7 @@ export default class TransactionViewBalance extends PureComponent { eventOpts: { category: 'Navigation', action: 'Home', - name: 'Clicked Send', + name: selectedToken ? 'Clicked Send: Token' : 'Clicked Send: Eth', }, }) history.push(SEND_ROUTE) From c80b295ccccc5d57a5dfe0ca8cc2d663609b7a26 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 8 Apr 2019 11:32:51 -0230 Subject: [PATCH 04/29] Only pass english function names to functionType metric --- .../confirm-deploy-contract.component.js | 2 +- .../confirm-send-ether.component.js | 2 +- .../confirm-transaction-base.component.js | 22 +++++++++---------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/ui/app/pages/confirm-deploy-contract/confirm-deploy-contract.component.js b/ui/app/pages/confirm-deploy-contract/confirm-deploy-contract.component.js index 9bc0daab9..c90ccc917 100644 --- a/ui/app/pages/confirm-deploy-contract/confirm-deploy-contract.component.js +++ b/ui/app/pages/confirm-deploy-contract/confirm-deploy-contract.component.js @@ -56,7 +56,7 @@ export default class ConfirmDeployContract extends Component { render () { return ( ) diff --git a/ui/app/pages/confirm-send-ether/confirm-send-ether.component.js b/ui/app/pages/confirm-send-ether/confirm-send-ether.component.js index 8daad675e..68280f624 100644 --- a/ui/app/pages/confirm-send-ether/confirm-send-ether.component.js +++ b/ui/app/pages/confirm-send-ether/confirm-send-ether.component.js @@ -30,7 +30,7 @@ export default class ConfirmSendEther extends Component { return ( this.handleEdit(confirmTransactionData)} /> diff --git a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js index 9e749322f..5cafe91c9 100644 --- a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js +++ b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js @@ -64,7 +64,7 @@ export default class ConfirmTransactionBase extends Component { updateGasAndCalculate: PropTypes.func, customGas: PropTypes.object, // Component props - action: PropTypes.string, + actionKey: PropTypes.string, contentComponent: PropTypes.node, dataComponent: PropTypes.node, detailsComponent: PropTypes.node, @@ -159,7 +159,7 @@ export default class ConfirmTransactionBase extends Component { } handleEditGas () { - const { onEditGas, showCustomizeGasModal, action, txData: { origin }, methodData = {} } = this.props + const { onEditGas, showCustomizeGasModal, actionKey, txData: { origin }, methodData = {} } = this.props this.context.metricsEvent({ eventOpts: { @@ -169,7 +169,7 @@ export default class ConfirmTransactionBase extends Component { }, customVariables: { recipientKnown: null, - functionType: action || getMethodName(methodData.name) || this.context.t('contractInteraction'), + functionType: actionKey || getMethodName(methodData.name) || 'contractInteraction', origin, }, }) @@ -292,7 +292,7 @@ export default class ConfirmTransactionBase extends Component { } handleEdit () { - const { txData, tokenData, tokenProps, onEdit, action, txData: { origin }, methodData = {} } = this.props + const { txData, tokenData, tokenProps, onEdit, actionKey, txData: { origin }, methodData = {} } = this.props this.context.metricsEvent({ eventOpts: { @@ -302,7 +302,7 @@ export default class ConfirmTransactionBase extends Component { }, customVariables: { recipientKnown: null, - functionType: action || getMethodName(methodData.name) || this.context.t('contractInteraction'), + functionType: actionKey || getMethodName(methodData.name) || 'contractInteraction', origin, }, }) @@ -331,7 +331,7 @@ export default class ConfirmTransactionBase extends Component { handleCancel () { const { metricsEvent } = this.context - const { onCancel, txData, cancelTransaction, history, clearConfirmTransaction, action, txData: { origin }, methodData = {} } = this.props + const { onCancel, txData, cancelTransaction, history, clearConfirmTransaction, actionKey, txData: { origin }, methodData = {} } = this.props if (onCancel) { metricsEvent({ @@ -342,7 +342,7 @@ export default class ConfirmTransactionBase extends Component { }, customVariables: { recipientKnown: null, - functionType: action || getMethodName(methodData.name) || this.context.t('contractInteraction'), + functionType: actionKey || getMethodName(methodData.name) || 'contractInteraction', origin, }, }) @@ -358,7 +358,7 @@ export default class ConfirmTransactionBase extends Component { handleSubmit () { const { metricsEvent } = this.context - const { txData: { origin }, sendTransaction, clearConfirmTransaction, txData, history, onSubmit, action, metaMetricsSendCount = 0, setMetaMetricsSendCount, methodData = {} } = this.props + const { txData: { origin }, sendTransaction, clearConfirmTransaction, txData, history, onSubmit, actionKey, metaMetricsSendCount = 0, setMetaMetricsSendCount, methodData = {} } = this.props const { submitting } = this.state if (submitting) { @@ -377,7 +377,7 @@ export default class ConfirmTransactionBase extends Component { }, customVariables: { recipientKnown: null, - functionType: action || getMethodName(methodData.name) || this.context.t('contractInteraction'), + functionType: actionKey || getMethodName(methodData.name) || 'contractInteraction', origin, }, }) @@ -517,7 +517,7 @@ export default class ConfirmTransactionBase extends Component { valid: propsValid = true, errorMessage, errorKey: propsErrorKey, - action, + actionKey, title, subtitle, hideSubtitle, @@ -543,7 +543,7 @@ export default class ConfirmTransactionBase extends Component { toName={toName} toAddress={toAddress} showEdit={onEdit && !isTxReprice} - action={action || getMethodName(name) || this.context.t('contractInteraction')} + action={this.context.t(actionKey) || getMethodName(name) || this.context.t('contractInteraction')} title={title} titleComponent={this.renderTitleComponent()} subtitle={subtitle} From 7b13e9ae2e1756aca4c2a9ffb7d23026353c1d27 Mon Sep 17 00:00:00 2001 From: "Brian R. Bondy" Date: Mon, 8 Apr 2019 15:43:34 -0400 Subject: [PATCH 05/29] Update porting_to_new_environment.md This MetamaskInpageProvider file was moved out into its own repo, this updates the link to point to that repo. --- docs/porting_to_new_environment.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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! From 24761326de652e14533c8f51498a25e875ab429b Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Tue, 9 Apr 2019 14:14:04 -0230 Subject: [PATCH 06/29] Don't inject web3 on sharefile.com --- app/scripts/contentscript.js | 1 + 1 file changed, 1 insertion(+) 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 From d7a2ea9a2b28678bf46dfb606187f4bbb7d22453 Mon Sep 17 00:00:00 2001 From: Thomas Huang Date: Wed, 10 Apr 2019 16:34:13 -0500 Subject: [PATCH 07/29] Add Localhost 8545 for network dropdown names --- app/_locales/en/messages.json | 3 +++ ui/app/components/app/dropdowns/network-dropdown.js | 2 ++ .../loading-network-screen.component.js | 2 ++ ui/app/components/app/network.js | 2 +- ui/app/pages/routes/index.js | 4 ++++ 5 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 154925d1a..f9d23f69e 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -308,6 +308,9 @@ "connectingToRinkeby": { "message": "Connecting to Rinkeby Test Network" }, + "connectingToLocalhost": { + "message": "Connecting to Localhost 8545" + }, "connectingToUnknown": { "message": "Connecting to Unknown Network" }, diff --git a/ui/app/components/app/dropdowns/network-dropdown.js b/ui/app/components/app/dropdowns/network-dropdown.js index 3d9037a06..7e645725c 100644 --- a/ui/app/components/app/dropdowns/network-dropdown.js +++ b/ui/app/components/app/dropdowns/network-dropdown.js @@ -285,6 +285,8 @@ 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 { name = provider.nickname || this.context.t('unknownNetwork') } 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..b79051d0b 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,8 @@ 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 { name = this.context.t('connectingTo', [providerId]) } diff --git a/ui/app/components/app/network.js b/ui/app/components/app/network.js index e18404f42..54065dd73 100644 --- a/ui/app/components/app/network.js +++ b/ui/app/components/app/network.js @@ -139,7 +139,7 @@ Network.prototype.render = function () { }, }), - h('.network-name', providerNick || context.t('privateNetwork')), + h('.network-name', providerName === 'localhost' ? context.t('localhost') : providerNick || context.t('privateNetwork')), h('i.fa.fa-chevron-down.fa-lg.network-caret'), ]) } diff --git a/ui/app/pages/routes/index.js b/ui/app/pages/routes/index.js index e06d88c90..37ff2df61 100644 --- a/ui/app/pages/routes/index.js +++ b/ui/app/pages/routes/index.js @@ -267,6 +267,8 @@ class Routes extends Component { name = this.context.t('connectingToKovan') } else if (providerName === 'rinkeby') { name = this.context.t('connectingToRinkeby') + } else if (providerName === 'localhost') { + name = this.context.t('connectingToLocalhost') } else { name = this.context.t('connectingTo', [providerId]) } @@ -288,6 +290,8 @@ class Routes extends Component { name = this.context.t('kovan') } else if (providerName === 'rinkeby') { name = this.context.t('rinkeby') + } else if (providerName === 'localhost') { + name = this.context.t('localhost') } else { name = this.context.t('unknownNetwork') } From a973a7420abed55998b88c339d1c6a1c53bd63e2 Mon Sep 17 00:00:00 2001 From: Paul Bouchon Date: Wed, 10 Apr 2019 18:37:15 -0400 Subject: [PATCH 08/29] feature: switch token pricing to CoinGecko API (#6424) --- app/scripts/controllers/token-rates.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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) From 7ceb1c63ccceeb631c148b9244987dba54f77b12 Mon Sep 17 00:00:00 2001 From: Etienne Dusseault Date: Thu, 11 Apr 2019 09:26:47 +0800 Subject: [PATCH 09/29] Added Chrome limited site access solution doc (#6422) --- docs/limited_site_access.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 docs/limited_site_access.md 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. From 2786932576a92f8e75ee798d91c8222741c774c9 Mon Sep 17 00:00:00 2001 From: Sneh Koul <35871990+Sneh1999@users.noreply.github.com> Date: Thu, 11 Apr 2019 10:50:03 -0700 Subject: [PATCH 10/29] repeated getSelectedAddress() func send.selectors.js removed (#6056) --- ui/app/components/app/send/send.container.js | 5 ++++- ui/app/components/app/send/send.selectors.js | 8 +------- ui/app/components/app/send/tests/send-container.test.js | 5 ++++- ui/app/components/app/send/tests/send-selectors.test.js | 9 --------- 4 files changed, 9 insertions(+), 18 deletions(-) diff --git a/ui/app/components/app/send/send.container.js b/ui/app/components/app/send/send.container.js index e65463b93..303639c76 100644 --- a/ui/app/components/app/send/send.container.js +++ b/ui/app/components/app/send/send.container.js @@ -2,6 +2,10 @@ import { connect } from 'react-redux' import SendEther from './send.component' import { withRouter } from 'react-router-dom' import { compose } from 'recompose' +const { + getSelectedAddress, +} = require('../../../selectors/selectors') + import { getAmountConversionRate, getBlockGasLimit, @@ -12,7 +16,6 @@ import { getGasTotal, getPrimaryCurrency, getRecentBlocks, - getSelectedAddress, getSelectedToken, getSelectedTokenContract, getSelectedTokenToFiatRate, diff --git a/ui/app/components/app/send/send.selectors.js b/ui/app/components/app/send/send.selectors.js index 2ec677ad1..89f047d50 100644 --- a/ui/app/components/app/send/send.selectors.js +++ b/ui/app/components/app/send/send.selectors.js @@ -5,6 +5,7 @@ const { } = require('../../../helpers/utils/conversion-util') const { getMetaMaskAccounts, + getSelectedAddress, } = require('../../../selectors/selectors') const { estimateGasPriceFromRecentBlocks, @@ -33,7 +34,6 @@ const selectors = { getPrimaryCurrency, getRecentBlocks, getSelectedAccount, - getSelectedAddress, getSelectedIdentity, getSelectedToken, getSelectedTokenContract, @@ -149,12 +149,6 @@ function getSelectedAccount (state) { return accounts[selectedAddress] } -function getSelectedAddress (state) { - const selectedAddress = state.metamask.selectedAddress || Object.keys(getMetaMaskAccounts(state))[0] - - return selectedAddress -} - function getSelectedIdentity (state) { const selectedAddress = getSelectedAddress(state) const identities = state.metamask.identities diff --git a/ui/app/components/app/send/tests/send-container.test.js b/ui/app/components/app/send/tests/send-container.test.js index 9538b67b3..9d7365ac9 100644 --- a/ui/app/components/app/send/tests/send-container.test.js +++ b/ui/app/components/app/send/tests/send-container.test.js @@ -35,7 +35,6 @@ proxyquire('../send.container.js', { getGasTotal: (s) => `mockGasTotal:${s}`, getPrimaryCurrency: (s) => `mockPrimaryCurrency:${s}`, getRecentBlocks: (s) => `mockRecentBlocks:${s}`, - getSelectedAddress: (s) => `mockSelectedAddress:${s}`, getSelectedToken: (s) => `mockSelectedToken:${s}`, getSelectedTokenContract: (s) => `mockTokenContract:${s}`, getSelectedTokenToFiatRate: (s) => `mockTokenToFiatRate:${s}`, @@ -47,11 +46,15 @@ proxyquire('../send.container.js', { getTokenBalance: (s) => `mockTokenBalance:${s}`, getQrCodeData: (s) => `mockQrCodeData:${s}`, }, + '../../../selectors/selectors': { + getSelectedAddress: (s) => `mockSelectedAddress:${s}`, + }, '../../../store/actions': actionSpies, '../../../ducks/send/send.duck': duckActionSpies, './send.utils.js': { calcGasTotal: (gasLimit, gasPrice) => gasLimit + gasPrice, }, + }) describe('send container', () => { diff --git a/ui/app/components/app/send/tests/send-selectors.test.js b/ui/app/components/app/send/tests/send-selectors.test.js index cdc86fe59..ccc126795 100644 --- a/ui/app/components/app/send/tests/send-selectors.test.js +++ b/ui/app/components/app/send/tests/send-selectors.test.js @@ -20,7 +20,6 @@ const { getPrimaryCurrency, getRecentBlocks, getSelectedAccount, - getSelectedAddress, getSelectedIdentity, getSelectedToken, getSelectedTokenContract, @@ -274,14 +273,6 @@ describe('send selectors', () => { }) }) - describe('getSelectedAddress()', () => { - it('should', () => { - assert.equal( - getSelectedAddress(mockState), - '0xd85a4b6a394794842887b8284293d69163007bbb' - ) - }) - }) describe('getSelectedIdentity()', () => { it('should return the identity object of the currently selected address', () => { From c4a3d4ea82ef5488c8c91db77beca85679447722 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Thu, 11 Apr 2019 21:33:33 -0230 Subject: [PATCH 11/29] Remove unneeded array cloning in getSendToAccounts selector The use of `Object.entries` here to map the accounts into a new array effectively produces a shallow clone of the array without guaranteeing the order of the original array (as object iteration order is implementation-specific and variable). From MDN [1]: > The **`Object.entries()`** method returns an array of a given object's own enumerable > string-keyed property `[key, value]` pairs, in the same order as that provided by a > `for...in` loop And also: > The ordering of the properties is the same as that given by looping over the > property values of the object manually. Both of which suggest that the iteration order is the same as `for...in`, which is to say that it's not specified. [2] [3] This changeset removes the cloning, keeping the shallow clone created the line before which preserves the order of the items in the array. [1]:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries [2]:https://stackoverflow.com/a/5525820/1267663 [3]:https://stackoverflow.com/a/30919039/1267663 --- ui/app/components/app/send/send.selectors.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ui/app/components/app/send/send.selectors.js b/ui/app/components/app/send/send.selectors.js index 89f047d50..6056dea21 100644 --- a/ui/app/components/app/send/send.selectors.js +++ b/ui/app/components/app/send/send.selectors.js @@ -240,9 +240,7 @@ function getSendTo (state) { function getSendToAccounts (state) { const fromAccounts = accountsWithSendEtherInfoSelector(state) const addressBookAccounts = getAddressBook(state) - const allAccounts = [...fromAccounts, ...addressBookAccounts] - // TODO: figure out exactly what the below returns and put a descriptive variable name on it - return Object.entries(allAccounts).map(([key, account]) => account) + return [...fromAccounts, ...addressBookAccounts] } function getSendWarnings (state) { From 7c38ad9356bdf87d4a1aed15ec31c8968ed49cb8 Mon Sep 17 00:00:00 2001 From: Esteban Mino Date: Fri, 12 Apr 2019 12:26:39 -0400 Subject: [PATCH 12/29] bump contract metadata --- package-lock.json | 97 +++++++++++++++++++++++++++++++++++++++++------ package.json | 2 +- 2 files changed, 86 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 11eab5718..88f1832ad 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": { @@ -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..2c81507ce 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "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", From 33836c04638bd0ef023ed913e4c8ec976ad3caae Mon Sep 17 00:00:00 2001 From: Thomas Huang Date: Tue, 16 Apr 2019 11:15:53 -0600 Subject: [PATCH 13/29] Set rpcTarget, nickname, and ticker when selecting one of the default networks --- app/scripts/controllers/network/network.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/scripts/controllers/network/network.js b/app/scripts/controllers/network/network.js index 47432c1e2..ab1198dd3 100644 --- a/app/scripts/controllers/network/network.js +++ b/app/scripts/controllers/network/network.js @@ -140,10 +140,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 } From fb22fb12cafec238a2143df6e94218c890e4ba4e Mon Sep 17 00:00:00 2001 From: Sneh Koul <35871990+Sneh1999@users.noreply.github.com> Date: Tue, 16 Apr 2019 10:59:11 -0700 Subject: [PATCH 14/29] Adds e2e test for most web3 methods that dapps use (#6160) * schema added * ui for the dapp added and schema.js changed according to the comments in PR * added tests for all web3 methods * Update run-all.sh * Update web3.spec.js to work with new onboarding flow * changes made according to the comments * Create stand alone script for web3 e2e tests. * Lint fixes for web3 e2e tests. --- package.json | 2 + test/e2e/beta/run-web3.sh | 9 + test/e2e/beta/web3.spec.js | 365 +++++++++++++++++++++++++++++++++++++ test/web3/index.html | 105 +++++++++++ test/web3/schema.js | 209 +++++++++++++++++++++ test/web3/web3.js | 34 ++++ 6 files changed, 724 insertions(+) create mode 100755 test/e2e/beta/run-web3.sh create mode 100644 test/e2e/beta/web3.spec.js create mode 100644 test/web3/index.html create mode 100644 test/web3/schema.js create mode 100644 test/web3/web3.js diff --git a/package.json b/package.json index 2c81507ce..c0355d12c 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", 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/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) + } + }) + }) + + }) + + }) + }) + From 92c03bdff2281b5901151ad0840b83e40dad73bc Mon Sep 17 00:00:00 2001 From: Chi Kei Chan Date: Tue, 16 Apr 2019 12:35:22 -0700 Subject: [PATCH 15/29] Update buttons & colors to match design system (#6446) * Refactoring button styles * renaming buttons * Add Link and Button styles * Update new btn styles and storybook * Fix tests * Change font weight; Update storybook * Fix linter --- test/integration/lib/confirm-sig-requests.js | 6 +- test/integration/lib/send-new-ui.js | 6 +- .../app/add-token-button/index.scss | 5 +- ui/app/components/app/coinbase-form.js | 69 ----- .../app/customize-gas-modal/index.js | 2 +- .../components/app/modal/modal.component.js | 2 +- .../app/modal/tests/modal.component.test.js | 4 +- .../app/modals/account-details-modal.js | 4 +- .../customize-gas/customize-gas.component.js | 2 +- .../app/modals/deposit-ether-modal.js | 2 +- .../app/modals/edit-account-name-modal.js | 2 +- .../app/modals/export-private-key-modal.js | 4 +- .../modals/hide-token-confirmation-modal.js | 4 +- .../app/modals/notification-modal.js | 4 +- ui/app/components/app/shapeshift-form.js | 2 +- ui/app/components/app/signature-request.js | 2 +- .../transaction-view-balance.component.js | 4 +- ui/app/components/app/wallet-view.js | 2 +- .../components/ui/button/button.component.js | 4 + ui/app/components/ui/button/button.stories.js | 47 ++-- ui/app/components/ui/button/buttons.scss | 244 ++++++++++++++++++ .../components/ui/page-container/index.scss | 8 - .../page-container-footer.component.js | 2 +- ui/app/css/itcss/components/buttons.scss | 230 ----------------- ui/app/css/itcss/components/index.scss | 2 +- ui/app/css/itcss/components/modal.scss | 2 + ui/app/css/itcss/generic/index.scss | 1 + ui/app/css/itcss/settings/typography.scss | 37 +++ ui/app/css/itcss/settings/variables.scss | 18 +- .../token-list-placeholder/index.scss | 3 +- .../confirm-add-suggested-token.component.js | 2 +- .../confirm-add-token.component.js | 2 +- .../connect-hardware/account-list.js | 2 +- .../connect-hardware/connect-screen.js | 2 +- .../create-account/import-account/json.js | 2 +- .../import-account/private-key.js | 2 +- ui/app/pages/create-account/new-account.js | 2 +- .../import-with-seed-phrase.component.js | 2 +- .../new-account/new-account.component.js | 2 +- .../unique-image/unique-image.component.js | 2 +- .../end-of-flow/end-of-flow.component.js | 2 +- .../confirm-seed-phrase.component.js | 2 +- .../reveal-seed-phrase.component.js | 2 +- .../select-action/select-action.component.js | 2 +- .../welcome/welcome.component.js | 2 +- ui/app/pages/keychains/reveal-seed.js | 2 +- ui/app/pages/mobile-sync/index.js | 2 +- .../advanced-tab/advanced-tab.component.js | 6 +- ui/app/pages/settings/info-tab/index.scss | 2 +- .../security-tab/security-tab.component.js | 6 +- ui/app/pages/settings/settings-tab/index.scss | 20 +- 51 files changed, 391 insertions(+), 401 deletions(-) delete mode 100644 ui/app/components/app/coinbase-form.js create mode 100644 ui/app/components/ui/button/buttons.scss delete mode 100644 ui/app/css/itcss/components/buttons.scss 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..ce470fc02 100644 --- a/test/integration/lib/send-new-ui.js +++ b/test/integration/lib/send-new-ui.js @@ -43,7 +43,7 @@ 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() @@ -88,7 +88,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() @@ -122,7 +122,7 @@ async function runSendFlowTest (assert, done) { 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/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..4434b8c25 100644 --- a/ui/app/components/app/customize-gas-modal/index.js +++ b/ui/app/components/app/customize-gas-modal/index.js @@ -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/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..178f45729 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 @@ -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 { }