From 947bfb7a118f3743e50fa2dd3c83e75d38c9207a Mon Sep 17 00:00:00 2001 From: David Walsh Date: Wed, 14 Oct 2020 16:48:09 -0500 Subject: [PATCH 01/18] Fix #9043 - Ensure QR code scanner works --- .../modals/qr-scanner/qr-scanner.component.js | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js b/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js index d78db3d14..330bd3fa4 100644 --- a/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js +++ b/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js @@ -104,15 +104,28 @@ export default class QrScanner extends Component { componentWillUnmount () { this.mounted = false clearTimeout(this.permissionChecker) - if (this.codeReader) { - this.codeReader.reset() + this.teardownCodeReader(); + } + + teardownCodeReader() { + if(this.codeReader) { + this.codeReader.reset(); + this.codeReader.stop(); + this.codeReader = null; } } initCamera = async () => { + // The `decodeFromInputVideoDevice` call prompts the browser to show + // the user the camera permission request. We must then call it again + // once we receive permission so that the video displays. + // Removing this teardown will create 2 video streams in Firefox, one + // of which the user will not be able to remove without refreshing the page. + this.teardownCodeReader(); this.codeReader = new BrowserQRCodeReader() try { await this.codeReader.getVideoInputDevices() + this.checkPermissions(); const content = await this.codeReader.decodeFromInputVideoDevice(undefined, 'video') const result = this.parseContent(content.text) if (!this.mounted) { @@ -162,7 +175,7 @@ export default class QrScanner extends Component { stopAndClose = () => { if (this.codeReader) { - this.codeReader.reset() + this.teardownCodeReader(); } this.props.hideModal() } @@ -170,7 +183,7 @@ export default class QrScanner extends Component { tryAgain = () => { clearTimeout(this.permissionChecker) if (this.codeReader) { - this.codeReader.reset() + this.teardownCodeReader() } this.setState(this.getInitialState(), () => { this.checkEnvironment() From 15654b3d0bb627c0ee4f62a13a8c2cba2a8f3c62 Mon Sep 17 00:00:00 2001 From: David Walsh Date: Wed, 14 Oct 2020 17:05:36 -0500 Subject: [PATCH 02/18] Prevent unnecessary teardown --- .../app/modals/qr-scanner/qr-scanner.component.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js b/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js index 330bd3fa4..cb95195a5 100644 --- a/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js +++ b/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js @@ -119,10 +119,11 @@ export default class QrScanner extends Component { // The `decodeFromInputVideoDevice` call prompts the browser to show // the user the camera permission request. We must then call it again // once we receive permission so that the video displays. - // Removing this teardown will create 2 video streams in Firefox, one - // of which the user will not be able to remove without refreshing the page. - this.teardownCodeReader(); - this.codeReader = new BrowserQRCodeReader() + // It's important to prevent this codeReader from being created twice; + // Firefox otherwise starts 2 video streams, one of which cannot be stopped + if(!this.codeReader) { + this.codeReader = new BrowserQRCodeReader() + } try { await this.codeReader.getVideoInputDevices() this.checkPermissions(); From 998748430182e33d30018ff6ff77796ab4689ad3 Mon Sep 17 00:00:00 2001 From: David Walsh Date: Wed, 14 Oct 2020 17:07:15 -0500 Subject: [PATCH 03/18] Fix lint --- .../modals/qr-scanner/qr-scanner.component.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js b/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js index cb95195a5..e49309adc 100644 --- a/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js +++ b/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js @@ -104,14 +104,14 @@ export default class QrScanner extends Component { componentWillUnmount () { this.mounted = false clearTimeout(this.permissionChecker) - this.teardownCodeReader(); + this.teardownCodeReader() } - teardownCodeReader() { - if(this.codeReader) { - this.codeReader.reset(); - this.codeReader.stop(); - this.codeReader = null; + teardownCodeReader () { + if (this.codeReader) { + this.codeReader.reset() + this.codeReader.stop() + this.codeReader = null } } @@ -121,12 +121,12 @@ export default class QrScanner extends Component { // once we receive permission so that the video displays. // It's important to prevent this codeReader from being created twice; // Firefox otherwise starts 2 video streams, one of which cannot be stopped - if(!this.codeReader) { + if (!this.codeReader) { this.codeReader = new BrowserQRCodeReader() } try { await this.codeReader.getVideoInputDevices() - this.checkPermissions(); + this.checkPermissions() const content = await this.codeReader.decodeFromInputVideoDevice(undefined, 'video') const result = this.parseContent(content.text) if (!this.mounted) { @@ -176,7 +176,7 @@ export default class QrScanner extends Component { stopAndClose = () => { if (this.codeReader) { - this.teardownCodeReader(); + this.teardownCodeReader() } this.props.hideModal() } From a53121a7635781df773d3f57e9f453743b1ff817 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 15 Oct 2020 11:18:30 -0230 Subject: [PATCH 04/18] Use `chainId` in account tracker (#9573) The `chainId` is now used by the account tracker to identify the current network, instead of the `networkId`. This should have no functional impact, aside from that different chains with the same `networkId` will now be correctly distinguished from each other. --- app/scripts/lib/account-tracker.js | 16 ++++++++-------- app/scripts/metamask-controller.js | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/scripts/lib/account-tracker.js b/app/scripts/lib/account-tracker.js index 2d97a1382..599baa86c 100644 --- a/app/scripts/lib/account-tracker.js +++ b/app/scripts/lib/account-tracker.js @@ -14,7 +14,7 @@ import log from 'loglevel' import pify from 'pify' import Web3 from 'web3' import SINGLE_CALL_BALANCES_ABI from 'single-call-balance-checker-abi' -import { MAINNET_NETWORK_ID, RINKEBY_NETWORK_ID, ROPSTEN_NETWORK_ID, KOVAN_NETWORK_ID } from '../controllers/network/enums' +import { MAINNET_CHAIN_ID, RINKEBY_CHAIN_ID, ROPSTEN_CHAIN_ID, KOVAN_CHAIN_ID } from '../controllers/network/enums' import { SINGLE_CALL_BALANCES_ADDRESS, @@ -61,7 +61,7 @@ export default class AccountTracker { }) // bind function for easier listener syntax this._updateForBlock = this._updateForBlock.bind(this) - this.network = opts.network + this.getCurrentChainId = opts.getCurrentChainId this.web3 = new Web3(this._provider) } @@ -196,22 +196,22 @@ export default class AccountTracker { async _updateAccounts () { const { accounts } = this.store.getState() const addresses = Object.keys(accounts) - const currentNetwork = this.network.getNetworkState() + const chainId = this.getCurrentChainId() - switch (currentNetwork) { - case MAINNET_NETWORK_ID.toString(): + switch (chainId) { + case MAINNET_CHAIN_ID: await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS) break - case RINKEBY_NETWORK_ID.toString(): + case RINKEBY_CHAIN_ID: await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS_RINKEBY) break - case ROPSTEN_NETWORK_ID.toString(): + case ROPSTEN_CHAIN_ID: await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN) break - case KOVAN_NETWORK_ID.toString(): + case KOVAN_CHAIN_ID: await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS_KOVAN) break diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index b4166bde5..f66091d80 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -168,7 +168,7 @@ export default class MetamaskController extends EventEmitter { this.accountTracker = new AccountTracker({ provider: this.provider, blockTracker: this.blockTracker, - network: this.networkController, + getCurrentChainId: this.networkController.getCurrentChainId.bind(this.networkController), }) // start and stop polling for balances based on activeControllerConnections From 971f5b005631b8bee5fc6f7485c1909557a679db Mon Sep 17 00:00:00 2001 From: David Walsh Date: Thu, 15 Oct 2020 10:36:15 -0500 Subject: [PATCH 05/18] Fix #9577 - Use contained button type for unlock button --- ui/app/pages/unlock-page/unlock-page.component.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/pages/unlock-page/unlock-page.component.js b/ui/app/pages/unlock-page/unlock-page.component.js index 5fffbf399..94a643263 100644 --- a/ui/app/pages/unlock-page/unlock-page.component.js +++ b/ui/app/pages/unlock-page/unlock-page.component.js @@ -122,7 +122,7 @@ export default class UnlockPage extends Component { style={style} disabled={!this.state.password} fullWidth - variant="raised" + variant="contained" size="large" onClick={this.handleSubmit} disableRipple From 003f1306d3a3bb6f35335f63964910f06f5bbc20 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 15 Oct 2020 13:09:13 -0230 Subject: [PATCH 06/18] Split the account tracker doc comments (#9574) The account tracker had one doc comment above the constructor that partially served to document the constructor, but mostly contained a type definition for the class itself. It has been split into two blocks; one for the class, one for the constructor. The constructor doc comment has also been expanded to document all constructor options. --- app/scripts/lib/account-tracker.js | 37 +++++++++++++++++------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/app/scripts/lib/account-tracker.js b/app/scripts/lib/account-tracker.js index 599baa86c..bb5563606 100644 --- a/app/scripts/lib/account-tracker.js +++ b/app/scripts/lib/account-tracker.js @@ -24,25 +24,30 @@ import { } from '../controllers/network/contract-addresses' import { bnToHex } from './util' +/** + * This module is responsible for tracking any number of accounts and caching their current balances & transaction + * counts. + * + * It also tracks transaction hashes, and checks their inclusion status on each new block. + * + * @typedef {Object} AccountTracker + * @property {Object} store The stored object containing all accounts to track, as well as the current block's gas limit. + * @property {Object} store.accounts The accounts currently stored in this AccountTracker + * @property {string} store.currentBlockGasLimit A hex string indicating the gas limit of the current block + * @property {Object} _provider A provider needed to create the EthQuery instance used within this AccountTracker. + * @property {EthQuery} _query An EthQuery instance used to access account information from the blockchain + * @property {BlockTracker} _blockTracker A BlockTracker instance. Needed to ensure that accounts and their info updates + * when a new block is created. + * @property {Object} _currentBlockNumber Reference to a property on the _blockTracker: the number (i.e. an id) of the the current block + * + */ export default class AccountTracker { /** - * This module is responsible for tracking any number of accounts and caching their current balances & transaction - * counts. - * - * It also tracks transaction hashes, and checks their inclusion status on each new block. - * - * @typedef {Object} AccountTracker - * @param {Object} opts - Initialize various properties of the class. - * @property {Object} store The stored object containing all accounts to track, as well as the current block's gas limit. - * @property {Object} store.accounts The accounts currently stored in this AccountTracker - * @property {string} store.currentBlockGasLimit A hex string indicating the gas limit of the current block - * @property {Object} _provider A provider needed to create the EthQuery instance used within this AccountTracker. - * @property {EthQuery} _query An EthQuery instance used to access account information from the blockchain - * @property {BlockTracker} _blockTracker A BlockTracker instance. Needed to ensure that accounts and their info updates - * when a new block is created. - * @property {Object} _currentBlockNumber Reference to a property on the _blockTracker: the number (i.e. an id) of the the current block - * + * @param {Object} opts - Options for initializing the controller + * @param {Object} opts.provider - An EIP-1193 provider instance that uses the current global network + * @param {Object} opts.blockTracker - A block tracker, which emits events for each new block + * @param {Function} opts.getCurrentChainId - A function that returns the `chainId` for the current global network */ constructor (opts = {}) { const initState = { From e546314e3fd6c8398b148392ecb49386ce962de5 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Fri, 16 Oct 2020 13:29:32 -0230 Subject: [PATCH 07/18] Update fee card storybook to utilize latest fee-card api (#9613) * Update fee card storybook to utilize latest fee-card api * Update ui/app/pages/swaps/fee-card/fee-card.stories.js Co-authored-by: Mark Stacey Co-authored-by: Mark Stacey --- .../pages/swaps/fee-card/fee-card.stories.js | 51 +++++++------------ 1 file changed, 18 insertions(+), 33 deletions(-) diff --git a/ui/app/pages/swaps/fee-card/fee-card.stories.js b/ui/app/pages/swaps/fee-card/fee-card.stories.js index db5029a95..9527dcda3 100644 --- a/ui/app/pages/swaps/fee-card/fee-card.stories.js +++ b/ui/app/pages/swaps/fee-card/fee-card.stories.js @@ -3,6 +3,15 @@ import { action } from '@storybook/addon-actions' import { text } from '@storybook/addon-knobs/react' import FeeCard from './fee-card' +const tokenApprovalTextComponent = ( + + ABC + +) + const containerStyle = { width: '300px', } @@ -24,19 +33,11 @@ export const WithAllProps = () => { fee: text('secondaryFee', '100 USD'), maxFee: text('secondaryMaxFee', '200 USD'), })} - maxFeeRow={({ - text: text('maxFeeText', 'Max Fee'), - linkText: text('maxFeeLinkText', 'Edit'), - tooltipText: text('maxFeeTooltipText', 'Click here to edit.'), - onClick: action('Clicked max fee row link'), - })} - thirdRow={({ - text: text('thirdRowText', 'Extra Option'), - linkText: text('thirdRowLinkText', 'Click Me'), - tooltipText: text('thirdRowTooltipText', 'Something happens if you click this'), - onClick: action('Clicked third row link'), - hide: false, - })} + onFeeCardMaxRowClick={action('Clicked max fee row link')} + tokenApprovalTextComponent={tokenApprovalTextComponent} + tokenApprovalSourceTokenSymbol="ABC" + onTokenApprovalClick={action('Clicked third row link')} + hideTokenApprovalRow={false} /> ) @@ -55,19 +56,8 @@ export const WithoutThirdRow = () => { fee: text('secondaryFee', '100 USD'), maxFee: text('secondaryMaxFee', '200 USD'), })} - maxFeeRow={({ - text: text('maxFeeText', 'Max Fee'), - linkText: text('maxFeeLinkText', 'Edit'), - tooltipText: text('maxFeeTooltipText', 'Click here to edit.'), - onClick: action('Clicked max fee row link'), - })} - thirdRow={({ - text: text('thirdRowText', 'Extra Option'), - linkText: text('thirdRowLinkText', 'Click Me'), - tooltipText: text('thirdRowTooltipText', 'Something happens if you click this'), - onClick: action('Clicked third row link'), - hide: true, - })} + onFeeCardMaxRowClick={action('Clicked max fee row link')} + hideTokenApprovalRow /> ) @@ -77,17 +67,12 @@ export const WithOnlyRequiredProps = () => { return (
) From 3191d9e01454fa01415256efeeb9d0d48b52cbd7 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Sat, 17 Oct 2020 06:43:51 -0230 Subject: [PATCH 08/18] Help users avoid insufficient gas prices in swaps (#9624) * Don't show average (slowest) button in custom gas modal while in swaps * In swaps, show warning in custom gas modal if price set below average price * Update ui/app/selectors/custom-gas.js Co-authored-by: Erik Marks <25517051+rekmarks@users.noreply.github.com> * Fix typo Co-authored-by: Erik Marks <25517051+rekmarks@users.noreply.github.com> --- .../gas-modal-page-container.container.js | 2 +- ui/app/selectors/custom-gas.js | 31 ++++++++++++++----- ui/app/selectors/tests/custom-gas.test.js | 7 ----- 3 files changed, 24 insertions(+), 16 deletions(-) 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 54465f674..52aaf5162 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 @@ -158,7 +158,7 @@ const mapStateToProps = (state, ownProps) => { newTotalFiat, currentTimeEstimate, blockTime: getBasicGasEstimateBlockTime(state), - customPriceIsSafe: isCustomPriceSafe(state), + customPriceIsSafe: isCustomPriceSafe(state, isSwap), maxModeOn, gasPriceButtonGroupProps: { buttonDataLoading, diff --git a/ui/app/selectors/custom-gas.js b/ui/app/selectors/custom-gas.js index 4c4968d7e..27be49af4 100644 --- a/ui/app/selectors/custom-gas.js +++ b/ui/app/selectors/custom-gas.js @@ -88,16 +88,31 @@ export function getSafeLowEstimate (state) { return safeLow } -export function isCustomPriceSafe (state) { +export function getAverageEstimate (state) { + const { + gas: { + basicEstimates: { + average, + }, + }, + } = state + + return average +} + +export function isCustomPriceSafe (state, averageIsSafe) { const safeLow = getSafeLowEstimate(state) + const average = getAverageEstimate(state) + const safeMinimumPrice = averageIsSafe ? average : safeLow + const customGasPrice = getCustomGasPrice(state) if (!customGasPrice) { return true } - if (safeLow === null) { - return null + if (safeMinimumPrice === null) { + return false } const customPriceSafe = conversionGreaterThan( @@ -107,7 +122,7 @@ export function isCustomPriceSafe (state) { fromDenomination: 'WEI', toDenomination: 'GWEI', }, - { value: safeLow, fromNumericBase: 'dec' }, + { value: safeMinimumPrice, fromNumericBase: 'dec' }, ) return customPriceSafe @@ -216,7 +231,7 @@ export function getRenderableBasicEstimateData (state, gasLimit, useFastestButto }, } = state - const slowEstimatData = { + const slowEstimateData = { gasEstimateType: GAS_ESTIMATE_TYPES.SLOW, feeInPrimaryCurrency: getRenderableEthFee(safeLow, gasLimit), feeInSecondaryCurrency: showFiat @@ -234,7 +249,7 @@ export function getRenderableBasicEstimateData (state, gasLimit, useFastestButto timeEstimate: avgWait && getRenderableTimeEstimate(avgWait), priceInHexWei: getGasPriceInHexWei(average), } - const fastEstimatData = { + const fastEstimateData = { gasEstimateType: GAS_ESTIMATE_TYPES.FAST, feeInPrimaryCurrency: getRenderableEthFee(fast, gasLimit), feeInSecondaryCurrency: showFiat @@ -254,8 +269,8 @@ export function getRenderableBasicEstimateData (state, gasLimit, useFastestButto } return useFastestButtons - ? [averageEstimateData, fastEstimatData, fastestEstimateData] - : [slowEstimatData, averageEstimateData, fastEstimatData] + ? [fastEstimateData, fastestEstimateData] + : [slowEstimateData, averageEstimateData, fastEstimateData] } export function getRenderableEstimateDataForSmallButtonsFromGWEI (state) { diff --git a/ui/app/selectors/tests/custom-gas.test.js b/ui/app/selectors/tests/custom-gas.test.js index f9d2e67f0..6bbc71cd9 100644 --- a/ui/app/selectors/tests/custom-gas.test.js +++ b/ui/app/selectors/tests/custom-gas.test.js @@ -346,13 +346,6 @@ describe('custom-gas selectors', function () { }, { expectedResult: [ - { - gasEstimateType: 'AVERAGE', - feeInPrimaryCurrency: '0.000147 ETH', - feeInSecondaryCurrency: '$0.38', - priceInHexWei: '0x1a13b8600', - timeEstimate: '~10 min 6 sec', - }, { gasEstimateType: 'FAST', feeInSecondaryCurrency: '$0.54', From 397e3a2c7fb1a4ad4bb92713a8b72a658dda4194 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Mon, 19 Oct 2020 05:54:59 -0230 Subject: [PATCH 09/18] Update swaps network fee tooltip (#9614) * Update network fee tooltip in fee-card to match latest designs * Clean up css classes and div --- app/_locales/en/messages.json | 10 ++++++- ui/app/components/ui/info-tooltip/index.scss | 19 ++++++++---- .../ui/info-tooltip/info-tooltip.js | 12 ++++++-- ui/app/pages/swaps/fee-card/fee-card.js | 30 +++++++++++++++---- ui/app/pages/swaps/fee-card/index.scss | 21 ++++++++++++- 5 files changed, 77 insertions(+), 15 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 3188d7894..edac431a5 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -1643,6 +1643,10 @@ "swapEstimatedNetworkFee": { "message": "Estimated network fee" }, + "swapEstimatedNetworkFeeSummary": { + "message": "The “$1” is what we expect the actual fee to be. The exact amount depends on network conditions.", + "description": "$1 will be the translation of swapEstimatedNetworkFee, with the font bolded" + }, "swapEstimatedNetworkFees": { "message": "Estimated network fees" }, @@ -1677,6 +1681,9 @@ "swapFinalizing": { "message": "Finalizing..." }, + "swapGasFeeSummary": { + "message": "The gas fee covers the cost of processing your swap and storing it on the Ethereum network. MetaMask does not profit from this fee." + }, "swapGetQuotes": { "message": "Get quotes" }, @@ -1705,7 +1712,8 @@ "message": "Transaction may fail, max slippage too low." }, "swapMaxNetworkFeeInfo": { - "message": "The Max network fee is the most you’ll pay to complete your transaction. The max fee helps ensure your Swap has the best chance of succeeding. MetaMask does not profit from network fees." + "message": "“$1” is the most you’ll spend. When the network is volatile this can be a large amount.", + "description": "$1 will be the translation of swapMaxNetworkFees, with the font bolded" }, "swapMaxNetworkFees": { "message": "Max network fee" diff --git a/ui/app/components/ui/info-tooltip/index.scss b/ui/app/components/ui/info-tooltip/index.scss index ba0ca6259..cdc52fb97 100644 --- a/ui/app/components/ui/info-tooltip/index.scss +++ b/ui/app/components/ui/info-tooltip/index.scss @@ -5,24 +5,29 @@ } } -.tippy-popper[x-placement^=top] .tippy-tooltip-info-theme [x-arrow] { +.tippy-popper[x-placement^=top] .tippy-tooltip-info-theme [x-arrow], +.tippy-popper[x-placement^=top] .tippy-tooltip-wideInfo-theme [x-arrow] { border-top-color: $white; } -.tippy-popper[x-placement^=right] .tippy-tooltip-info-theme [x-arrow] { +.tippy-popper[x-placement^=right] .tippy-tooltip-info-theme [x-arrow], +.tippy-popper[x-placement^=right] .tippy-tooltip-wideInfo-theme [x-arrow] { border-right-color: $white; } -.tippy-popper[x-placement^=left] .tippy-tooltip-info-theme [x-arrow] { +.tippy-popper[x-placement^=left] .tippy-tooltip-info-theme [x-arrow], +.tippy-popper[x-placement^=left] .tippy-tooltip-wideInfo-theme [x-arrow] { border-left-color: $white; } -.tippy-popper[x-placement^=bottom] .tippy-tooltip-info-theme [x-arrow] { +.tippy-popper[x-placement^=bottom] .tippy-tooltip-info-theme [x-arrow], +.tippy-popper[x-placement^=bottom] .tippy-tooltip-wideInfo-theme [x-arrow] { border-bottom-color: $white; } .tippy-tooltip { - &#{&}-info-theme { + &#{&}-info-theme, + &#{&}-wideInfo-theme { background: white; color: black; box-shadow: 0 0 14px rgba(0, 0, 0, 0.18); @@ -38,4 +43,8 @@ color: $Grey-500; } } + + &#{&}-wideInfo-theme { + max-width: 260px; + } } diff --git a/ui/app/components/ui/info-tooltip/info-tooltip.js b/ui/app/components/ui/info-tooltip/info-tooltip.js index 73bf356c5..42b8af75d 100644 --- a/ui/app/components/ui/info-tooltip/info-tooltip.js +++ b/ui/app/components/ui/info-tooltip/info-tooltip.js @@ -1,5 +1,6 @@ import React from 'react' import PropTypes from 'prop-types' +import classnames from 'classnames' import Tooltip from '../tooltip' const positionArrowClassMap = { @@ -12,17 +13,21 @@ const positionArrowClassMap = { export default function InfoTooltip ({ contentText = '', position = '', + containerClassName, + wrapperClassName, + wide, }) { return (
@@ -33,4 +38,7 @@ export default function InfoTooltip ({ InfoTooltip.propTypes = { contentText: PropTypes.string, position: PropTypes.oneOf(['top', 'left', 'bottom', 'right']), + wide: PropTypes.bool, + containerClassName: PropTypes.string, + wrapperClassName: PropTypes.string, } diff --git a/ui/app/pages/swaps/fee-card/fee-card.js b/ui/app/pages/swaps/fee-card/fee-card.js index 61c173015..67f2ee807 100644 --- a/ui/app/pages/swaps/fee-card/fee-card.js +++ b/ui/app/pages/swaps/fee-card/fee-card.js @@ -22,6 +22,30 @@ export default function FeeCard ({
{t('swapEstimatedNetworkFee')}
+ +

{ t('swapGasFeeSummary') }

+

{ t('swapEstimatedNetworkFeeSummary', [ + + { t('swapEstimatedNetworkFee') } + , + ]) } +

+

{ t('swapMaxNetworkFeeInfo', [ + + { t('swapMaxNetworkFees') } + , + ]) } +

+ + ) + } + containerClassName="fee-card__info-tooltip-content-container" + wrapperClassName="fee-card__row-label fee-card__info-tooltip-container" + wide + />
@@ -42,12 +66,6 @@ export default function FeeCard ({
{t('edit')}
-
- -
diff --git a/ui/app/pages/swaps/fee-card/index.scss b/ui/app/pages/swaps/fee-card/index.scss index 79e08c4ad..262b7a5eb 100644 --- a/ui/app/pages/swaps/fee-card/index.scss +++ b/ui/app/pages/swaps/fee-card/index.scss @@ -27,7 +27,7 @@ &__row-header-text, &__row-header-text--bold { - margin-right: 6px; + margin-right: 4px; cursor: pointer; } @@ -56,6 +56,21 @@ } } + &__info-tooltip-container { + height: 10px; + width: 10px; + justify-content: center; + margin-top: 2px; + } + + &__info-tooltip-paragraph { + margin-bottom: 8px; + } + + &__info-tooltip-paragraph:last-of-type { + margin-bottom: 0; + } + &__row-fee { margin-right: 4px; } @@ -109,6 +124,10 @@ &__row-header-primary--bold { font-weight: bold; } + + &__bold { + font-weight: bold; + } } .info-tooltip { From 7925a767b8f78a27f2370d29e6f5b5c00e2b74d3 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Mon, 19 Oct 2020 10:41:23 -0230 Subject: [PATCH 10/18] Add a minimumGasLimit to the original gas customization modal (#9623) * Add a minimumGasLimit to the gas customization modal in swaps * Update app/_locales/en/messages.json Co-authored-by: Mark Stacey * Set default for minimum gas limit in gas-modal-page-container.container and make required in sub components * Update unit tests * Default value for minimumGasLimit in advanced-gas-inputs.component.js * Preserve existing gasLimitTooLow message key by creating new gasLimitTooLowWithDynamicFee * Fix failing unit test Co-authored-by: Mark Stacey Co-authored-by: Erik Marks --- app/_locales/en/messages.json | 4 ++++ .../advanced-gas-inputs.component.js | 15 +++++++++++---- .../tests/advanced-gas-input-component.test.js | 3 ++- .../advanced-tab-content.component.js | 3 +++ .../gas-modal-page-container.component.js | 5 ++++- .../gas-modal-page-container.container.js | 6 +++++- .../gas-modal-page-container-container.test.js | 2 ++ ui/app/pages/swaps/view-quote/view-quote.js | 10 +++++----- 8 files changed, 36 insertions(+), 12 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index edac431a5..9987defdf 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -708,6 +708,10 @@ "gasLimitTooLow": { "message": "Gas limit must be at least 21000" }, + "gasLimitTooLowWithDynamicFee": { + "message": "Gas limit must be at least $1", + "description": "$1 is the custom gas limit, in decimal." + }, "gasPrice": { "message": "Gas Price (GWEI)" }, diff --git a/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js b/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js index 75319090d..6f299cbaa 100644 --- a/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js +++ b/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types' import classnames from 'classnames' import { debounce } from 'lodash' import Tooltip from '../../../ui/tooltip' +import { MIN_GAS_LIMIT_DEC } from '../../../../pages/send/send.constants' export default class AdvancedGasInputs extends Component { static contextTypes = { @@ -18,6 +19,11 @@ export default class AdvancedGasInputs extends Component { customPriceIsSafe: PropTypes.bool, isSpeedUp: PropTypes.bool, customGasLimitMessage: PropTypes.string, + minimumGasLimit: PropTypes.number, + } + + static defaultProps = { + minimumGasLimit: MIN_GAS_LIMIT_DEC, } constructor (props) { @@ -84,7 +90,7 @@ export default class AdvancedGasInputs extends Component { return {} } - gasLimitError ({ insufficientBalance, gasLimit }) { + gasLimitError ({ insufficientBalance, gasLimit, minimumGasLimit }) { const { t } = this.context if (insufficientBalance) { @@ -92,9 +98,9 @@ export default class AdvancedGasInputs extends Component { errorText: t('insufficientBalance'), errorType: 'error', } - } else if (gasLimit < 21000) { + } else if (gasLimit < minimumGasLimit) { return { - errorText: t('gasLimitTooLow'), + errorText: t('gasLimitTooLowWithDynamicFee', [minimumGasLimit]), errorType: 'error', } } @@ -153,6 +159,7 @@ export default class AdvancedGasInputs extends Component { customPriceIsSafe, isSpeedUp, customGasLimitMessage, + minimumGasLimit, } = this.props const { gasPrice, @@ -172,7 +179,7 @@ export default class AdvancedGasInputs extends Component { const { errorText: gasLimitErrorText, errorType: gasLimitErrorType, - } = this.gasLimitError({ insufficientBalance, gasLimit }) + } = this.gasLimitError({ insufficientBalance, gasLimit, minimumGasLimit }) const gasLimitErrorComponent = gasLimitErrorType ? (
{ gasLimitErrorText } diff --git a/ui/app/components/app/gas-customization/advanced-gas-inputs/tests/advanced-gas-input-component.test.js b/ui/app/components/app/gas-customization/advanced-gas-inputs/tests/advanced-gas-input-component.test.js index a18e0e114..b47a224a6 100644 --- a/ui/app/components/app/gas-customization/advanced-gas-inputs/tests/advanced-gas-input-component.test.js +++ b/ui/app/components/app/gas-customization/advanced-gas-inputs/tests/advanced-gas-input-component.test.js @@ -17,6 +17,7 @@ describe('Advanced Gas Inputs', function () { insufficientBalance: false, customPriceIsSafe: true, isSpeedUp: false, + minimumGasLimit: 21000, } beforeEach(function () { @@ -91,7 +92,7 @@ describe('Advanced Gas Inputs', function () { assert.equal(renderError.length, 2) assert.equal(renderError.at(0).text(), 'zeroGasPriceOnSpeedUpError') - assert.equal(renderError.at(1).text(), 'gasLimitTooLow') + assert.equal(renderError.at(1).text(), 'gasLimitTooLowWithDynamicFee') }) it('warns when custom gas price is too low', function () { diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js index 255012392..176fd9b12 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js @@ -27,6 +27,7 @@ export default class AdvancedTabContent extends Component { isSpeedUp: PropTypes.bool, isEthereumNetwork: PropTypes.bool, customGasLimitMessage: PropTypes.string, + minimumGasLimit: PropTypes.number.isRequired, } renderDataSummary (transactionFee, timeRemaining) { @@ -67,6 +68,7 @@ export default class AdvancedTabContent extends Component { transactionFee, isEthereumNetwork, customGasLimitMessage, + minimumGasLimit, } = this.props return ( @@ -83,6 +85,7 @@ export default class AdvancedTabContent extends Component { customPriceIsSafe={customPriceIsSafe} isSpeedUp={isSpeedUp} customGasLimitMessage={customGasLimitMessage} + minimumGasLimit={minimumGasLimit} />
{ isEthereumNetwork diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js b/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js index 061f43a95..249b29ca7 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js @@ -53,7 +53,8 @@ export default class GasModalPageContainer extends Component { customTotalSupplement: PropTypes.string, isSwap: PropTypes.bool, value: PropTypes.string, - conversionRate: PropTypes.number, + conversionRate: PropTypes.string, + minimumGasLimit: PropTypes.number.isRequired, } state = { @@ -98,6 +99,7 @@ export default class GasModalPageContainer extends Component { }, isEthereumNetwork, customGasLimitMessage, + minimumGasLimit, } = this.props return ( @@ -116,6 +118,7 @@ export default class GasModalPageContainer extends Component { isSpeedUp={isSpeedUp} isRetry={isRetry} isEthereumNetwork={isEthereumNetwork} + minimumGasLimit={minimumGasLimit} /> ) } 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 52aaf5162..c2275c247 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 @@ -62,6 +62,7 @@ import { calcGasTotal, isBalanceSufficient, } from '../../../../pages/send/send.utils' +import { MIN_GAS_LIMIT_DEC } from '../../../../pages/send/send.constants' import { calcMaxAmount } from '../../../../pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.utils' import GasModalPageContainer from './gas-modal-page-container.component' @@ -75,6 +76,7 @@ const mapStateToProps = (state, ownProps) => { customTotalSupplement = '', extraInfoRow = null, useFastestButtons = false, + minimumGasLimit = MIN_GAS_LIMIT_DEC, } = modalProps || {} const { transaction = {} } = ownProps const selectedTransaction = isSwap @@ -202,6 +204,7 @@ const mapStateToProps = (state, ownProps) => { conversionRate, value, customTotalSupplement, + minimumGasLimit, } } @@ -264,6 +267,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { tokenBalance, customGasLimit, transaction, + minimumGasLimit, } = stateProps const { hideGasButtonGroup: dispatchHideGasButtonGroup, @@ -333,7 +337,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { disableSave: ( insufficientBalance || (isSpeedUp && customGasPrice === 0) || - customGasLimit < 21000 + customGasLimit < minimumGasLimit ), } } diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js b/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js index 57514e5fa..00e15f943 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js @@ -63,6 +63,7 @@ describe('gas-modal-page-container container', function () { id: 34, }, extraInfoRow: { label: 'mockLabel', value: 'mockValue' }, + minimumGasLimit: 21000, }, }, }, @@ -170,6 +171,7 @@ describe('gas-modal-page-container container', function () { id: 34, }, value: '0x640000000000000', + minimumGasLimit: 21000, } const baseMockOwnProps = { transaction: { id: 34 } } const tests = [ diff --git a/ui/app/pages/swaps/view-quote/view-quote.js b/ui/app/pages/swaps/view-quote/view-quote.js index a94b2319b..371e75666 100644 --- a/ui/app/pages/swaps/view-quote/view-quote.js +++ b/ui/app/pages/swaps/view-quote/view-quote.js @@ -131,12 +131,11 @@ export default function ViewQuote () { .round(0) .toString(16) - const maxGasLimit = (customMaxGas || - hexMax( - (`0x${decimalToHex(usedQuote?.maxGas || 0)}`), - usedGasLimitWithMultiplier, - ) + const nonCustomMaxGasLimit = hexMax( + (`0x${decimalToHex(usedQuote?.maxGas || 0)}`), + usedGasLimitWithMultiplier, ) + const maxGasLimit = customMaxGas || nonCustomMaxGasLimit const gasTotalInWeiHex = calcGasTotal(maxGasLimit, gasPrice) @@ -394,6 +393,7 @@ export default function ViewQuote () { : null ), useFastestButtons: true, + minimumGasLimit: Number(hexToDecimal(nonCustomMaxGasLimit)), })) const tokenApprovalTextComponent = ( From b1adc0d1e8d37b7cea74dff4a81bc5e6a175b2e3 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Mon, 19 Oct 2020 12:15:21 -0230 Subject: [PATCH 11/18] Fix use transaction time remaining (#9630) * Only format timeRemaining if in useTransactionTimeRemaining if it is truthy * Only setTimeRemaining in useTransactionTimeRemaining if submitted time is a number * Use isSubmitted as a check, instead of isPending and submittedTime, before calculating timeRemaining --- .../transaction-list-item.component.js | 3 ++- .../tests/useTransactionDisplayData.test.js | 1 + ui/app/hooks/useTransactionDisplayData.js | 3 +++ ui/app/hooks/useTransactionTimeRemaining.js | 18 ++++++++++-------- .../pages/swaps/awaiting-swap/awaiting-swap.js | 12 ++++++++++-- 5 files changed, 26 insertions(+), 11 deletions(-) diff --git a/ui/app/components/app/transaction-list-item/transaction-list-item.component.js b/ui/app/components/app/transaction-list-item/transaction-list-item.component.js index 9f54202f2..9953df9d4 100644 --- a/ui/app/components/app/transaction-list-item/transaction-list-item.component.js +++ b/ui/app/components/app/transaction-list-item/transaction-list-item.component.js @@ -50,9 +50,10 @@ export default function TransactionListItem ({ transactionGroup, isEarliestNonce displayedStatusKey, isPending, senderAddress, + isSubmitted, } = useTransactionDisplayData(transactionGroup) - const timeRemaining = useTransactionTimeRemaining(isPending, isEarliestNonce, submittedTime, gasPrice) + const timeRemaining = useTransactionTimeRemaining(isSubmitted, isEarliestNonce, submittedTime, gasPrice) const isSignatureReq = category === TRANSACTION_CATEGORY_SIGNATURE_REQUEST const isApproval = category === TRANSACTION_CATEGORY_APPROVAL diff --git a/ui/app/hooks/tests/useTransactionDisplayData.test.js b/ui/app/hooks/tests/useTransactionDisplayData.test.js index 5291a3023..d44eb77ae 100644 --- a/ui/app/hooks/tests/useTransactionDisplayData.test.js +++ b/ui/app/hooks/tests/useTransactionDisplayData.test.js @@ -27,6 +27,7 @@ const expectedResults = [ secondaryCurrency: '-1 ETH', isPending: false, displayedStatusKey: 'confirmed', + isSubmitted: false, }, { title: 'Send ETH', diff --git a/ui/app/hooks/useTransactionDisplayData.js b/ui/app/hooks/useTransactionDisplayData.js index fa6f5773c..de027d41e 100644 --- a/ui/app/hooks/useTransactionDisplayData.js +++ b/ui/app/hooks/useTransactionDisplayData.js @@ -23,6 +23,7 @@ import { TOKEN_CATEGORY_HASH, SWAP, SWAP_APPROVAL, + SUBMITTED_STATUS, } from '../helpers/constants/transactions' import { getTokens } from '../ducks/metamask/metamask' import { useI18nContext } from './useI18nContext' @@ -74,6 +75,7 @@ export function useTransactionDisplayData (transactionGroup) { const displayedStatusKey = getStatusKey(primaryTransaction) const isPending = displayedStatusKey in PENDING_STATUS_HASH + const isSubmitted = displayedStatusKey === SUBMITTED_STATUS const primaryValue = primaryTransaction.txParams?.value let prefix = '-' @@ -213,5 +215,6 @@ export function useTransactionDisplayData (transactionGroup) { ) ? undefined : secondaryCurrency, displayedStatusKey, isPending, + isSubmitted, } } diff --git a/ui/app/hooks/useTransactionTimeRemaining.js b/ui/app/hooks/useTransactionTimeRemaining.js index db3e7f890..50f413d05 100644 --- a/ui/app/hooks/useTransactionTimeRemaining.js +++ b/ui/app/hooks/useTransactionTimeRemaining.js @@ -26,7 +26,7 @@ function calcTransactionTimeRemaining (initialTimeEstimate, submittedTime) { * returns a string representing the number of minutes predicted for the transaction to be * completed. Only returns this prediction if the transaction is the earliest pending * transaction, and the feature flag for showing timing is enabled. - * @param {bool} isPending - is the transaction currently pending + * @param {bool} isSubmitted - is the transaction currently in the 'submitted' state * @param {bool} isEarliestNonce - is this transaction the earliest nonce in list * @param {number} submittedTime - the timestamp for when the transaction was submitted * @param {number} currentGasPrice - gas price to use for calculation of time @@ -34,7 +34,7 @@ function calcTransactionTimeRemaining (initialTimeEstimate, submittedTime) { * @returns {string | undefined} i18n formatted string if applicable */ export function useTransactionTimeRemaining ( - isPending, + isSubmitted, isEarliestNonce, submittedTime, currentGasPrice, @@ -73,7 +73,7 @@ export function useTransactionTimeRemaining ( if ( (isMainNet && (transactionTimeFeatureActive || forceAllow)) && - isPending && + isSubmitted && isEarliestNonce && !isNaN(initialTimeEstimate) ) { @@ -93,10 +93,10 @@ export function useTransactionTimeRemaining ( isMainNet, transactionTimeFeatureActive, isEarliestNonce, - isPending, submittedTime, initialTimeEstimate, forceAllow, + isSubmitted, ]) // there are numerous checks to determine if time should be displayed. @@ -104,8 +104,10 @@ export function useTransactionTimeRemaining ( // User is currently not on the mainnet // User does not have the transactionTime feature flag enabled // The transaction is not pending, or isn't the earliest nonce - const usedFormat = dontFormat - ? timeRemaining - : rtf.format(timeRemaining, 'minute') - return timeRemaining ? usedFormat : undefined + if (timeRemaining && dontFormat) { + return timeRemaining + } else if (timeRemaining) { + return rtf.format(timeRemaining, 'minute') + } + return undefined } diff --git a/ui/app/pages/swaps/awaiting-swap/awaiting-swap.js b/ui/app/pages/swaps/awaiting-swap/awaiting-swap.js index 1ee4f78c3..40c9ff0de 100644 --- a/ui/app/pages/swaps/awaiting-swap/awaiting-swap.js +++ b/ui/app/pages/swaps/awaiting-swap/awaiting-swap.js @@ -19,7 +19,7 @@ import { useTransactionTimeRemaining } from '../../../hooks/useTransactionTimeRe import { usePrevious } from '../../../hooks/usePrevious' import Mascot from '../../../components/ui/mascot' import PulseLoader from '../../../components/ui/pulse-loader' -import { getBlockExplorerUrlForTx } from '../../../helpers/utils/transactions.util' +import { getBlockExplorerUrlForTx, getStatusKey } from '../../../helpers/utils/transactions.util' import CountdownTimer from '../countdown-timer' import { QUOTES_EXPIRED_ERROR, @@ -28,6 +28,7 @@ import { QUOTES_NOT_AVAILABLE_ERROR, OFFLINE_FOR_MAINTENANCE, } from '../../../helpers/constants/swaps' +import { SUBMITTED_STATUS } from '../../../helpers/constants/transactions' import { ASSET_ROUTE, DEFAULT_ROUTE } from '../../../helpers/constants/routes' import { getRenderableGasFeesForQuote } from '../swaps.util' @@ -96,7 +97,14 @@ export default function AwaitingSwap ({ rpcPrefs, ) - const timeRemaining = useTransactionTimeRemaining(true, true, tradeTxData?.submittedTime, usedGasPrice, true, true) + const timeRemaining = useTransactionTimeRemaining( + getStatusKey(tradeTxData) === SUBMITTED_STATUS, + true, + tradeTxData?.submittedTime, + usedGasPrice, + true, + true, + ) const previousTimeRemaining = usePrevious(timeRemaining) const timeRemainingIsNumber = typeof timeRemaining === 'number' && !isNaN(timeRemaining) const previousTimeRemainingIsNumber = typeof previousTimeRemaining === 'number' && !isNaN(previousTimeRemaining) From d44c03b8821c0974406b39b36ddba45df75c63bb Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Mon, 19 Oct 2020 15:21:36 -0230 Subject: [PATCH 12/18] Cast MIN_GAS_LIMIT_DEC to number before passing to minimumGasLimit (#9636) --- .../advanced-gas-inputs/advanced-gas-inputs.component.js | 2 +- .../gas-modal-page-container.container.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js b/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js index 6f299cbaa..5886f75d9 100644 --- a/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js +++ b/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js @@ -23,7 +23,7 @@ export default class AdvancedGasInputs extends Component { } static defaultProps = { - minimumGasLimit: MIN_GAS_LIMIT_DEC, + minimumGasLimit: Number(MIN_GAS_LIMIT_DEC), } constructor (props) { 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 c2275c247..2be9e0067 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 @@ -76,7 +76,7 @@ const mapStateToProps = (state, ownProps) => { customTotalSupplement = '', extraInfoRow = null, useFastestButtons = false, - minimumGasLimit = MIN_GAS_LIMIT_DEC, + minimumGasLimit = Number(MIN_GAS_LIMIT_DEC), } = modalProps || {} const { transaction = {} } = ownProps const selectedTransaction = isSwap From c8526bc687bbaa9d0b25af4d112e6a42f8745da4 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Mon, 19 Oct 2020 17:15:32 -0230 Subject: [PATCH 13/18] Update view-quote designs to better represent the metamask fee (#9633) * Update view-quote designs to better represent the metamask fee * Code clean up * Copy updates --- app/_locales/en/messages.json | 6 ++++- .../pages/swaps/main-quote-summary/index.scss | 5 ++-- .../main-quote-summary/quote-backdrop.js | 12 ++++----- ui/app/pages/swaps/view-quote/index.scss | 27 ++++++++++++------- ui/app/pages/swaps/view-quote/view-quote.js | 10 +++++++ 5 files changed, 41 insertions(+), 19 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 9987defdf..a4710ccbf 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -1729,7 +1729,7 @@ "message": "MetaMask fee" }, "swapMetaMaskFeeDescription": { - "message": "A service fee of $1% is automatically factored into each quote, which supports ongoing development to make MetaMask even better.", + "message": "We find the best price from the top liquidity sources, every time. A fee of $1% is automatically factored into each quote, which supports ongoing development to make MetaMask even better.", "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number." }, "swapNQuotesAvailable": { @@ -1753,6 +1753,10 @@ "swapQuoteDetailsSlippageInfo": { "message": "If the price changes between the time your order is placed and confirmed it’s called \"slippage\". Your Swap will automatically cancel if slippage exceeds your \"max slippage\" setting." }, + "swapQuoteIncludesRate": { + "message": "Quote includes a $1% MetaMask fee", + "description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number." + }, "swapQuoteNofN": { "message": "Quote $1 of $2", "description": "A count of loaded quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of quotes to load." diff --git a/ui/app/pages/swaps/main-quote-summary/index.scss b/ui/app/pages/swaps/main-quote-summary/index.scss index ff53942a0..7993ea724 100644 --- a/ui/app/pages/swaps/main-quote-summary/index.scss +++ b/ui/app/pages/swaps/main-quote-summary/index.scss @@ -22,7 +22,7 @@ &__quote-backdrop { width: 310px; - height: 179.15px; + height: 164px; } &__details { @@ -49,13 +49,14 @@ } &__quote-details-top { - height: 137px; + height: 94px; display: flex; flex-flow: column; justify-content: center; align-items: center; width: 100%; padding: 12px; + padding-top: 2px; margin-top: 4px; } diff --git a/ui/app/pages/swaps/main-quote-summary/quote-backdrop.js b/ui/app/pages/swaps/main-quote-summary/quote-backdrop.js index f8f06ff33..1a01fa0ca 100644 --- a/ui/app/pages/swaps/main-quote-summary/quote-backdrop.js +++ b/ui/app/pages/swaps/main-quote-summary/quote-backdrop.js @@ -5,13 +5,13 @@ export default function QuotesBackdrop ({ withTopTab, }) { return ( - + - - {withTopTab && } + + {withTopTab && } - + @@ -20,11 +20,11 @@ export default function QuotesBackdrop ({ - + - + diff --git a/ui/app/pages/swaps/view-quote/index.scss b/ui/app/pages/swaps/view-quote/index.scss index 7a6f8cb2d..1b76853fb 100644 --- a/ui/app/pages/swaps/view-quote/index.scss +++ b/ui/app/pages/swaps/view-quote/index.scss @@ -13,6 +13,7 @@ height: 100%; padding-left: 20px; padding-right: 20px; + justify-content: space-between; @media screen and (max-width: 576px) { overflow-y: auto; @@ -38,16 +39,11 @@ &__view-other-button-container { border-radius: 28px; - margin-top: 38px; width: 100%; position: relative; display: flex; align-items: center; justify-content: center; - - @media screen and (min-width: 576px) { - margin-top: auto; - } } &__view-other-button, @@ -131,13 +127,9 @@ &__fee-card-container { width: 100%; - margin-top: auto; + margin-top: 8px; margin-bottom: 8px; - @media screen and (max-width: 576px) { - margin-top: 16px; - } - @media screen and (min-width: 576px) { margin-bottom: 0; @@ -158,4 +150,19 @@ margin-top: 8px; } } + + &__metamask-rate { + display: flex; + margin-top: 8%; + } + + &__metamask-rate-text { + @include H7; + + color: $Grey-500; + } + + &__metamask-rate-info-icon { + margin-left: 4px; + } } diff --git a/ui/app/pages/swaps/view-quote/view-quote.js b/ui/app/pages/swaps/view-quote/view-quote.js index 371e75666..3ebb16803 100644 --- a/ui/app/pages/swaps/view-quote/view-quote.js +++ b/ui/app/pages/swaps/view-quote/view-quote.js @@ -72,6 +72,7 @@ import { useTokenTracker } from '../../../hooks/useTokenTracker' import { QUOTES_EXPIRED_ERROR } from '../../../helpers/constants/swaps' import CountdownTimer from '../countdown-timer' import SwapsFooter from '../swaps-footer' +import InfoTooltip from '../../../components/ui/info-tooltip' export default function ViewQuote () { const history = useHistory() @@ -199,6 +200,7 @@ export default function ViewQuote () { sourceTokenDecimals, sourceTokenSymbol, sourceTokenValue, + metaMaskFee, } = renderableDataForUsedQuote const { feeInFiat, feeInEth } = getRenderableGasFeesForQuote( @@ -494,6 +496,14 @@ export default function ViewQuote () {
+
+

{ t('swapQuoteIncludesRate', [metaMaskFee]) }

+ +
Date: Mon, 19 Oct 2020 18:29:48 -0230 Subject: [PATCH 14/18] getStatusKey to return an empty string when passed a falsy value (#9640) --- ui/app/pages/swaps/awaiting-swap/awaiting-swap.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/app/pages/swaps/awaiting-swap/awaiting-swap.js b/ui/app/pages/swaps/awaiting-swap/awaiting-swap.js index 40c9ff0de..1ddb5252f 100644 --- a/ui/app/pages/swaps/awaiting-swap/awaiting-swap.js +++ b/ui/app/pages/swaps/awaiting-swap/awaiting-swap.js @@ -97,8 +97,9 @@ export default function AwaitingSwap ({ rpcPrefs, ) + const statusKey = tradeTxData && getStatusKey(tradeTxData) const timeRemaining = useTransactionTimeRemaining( - getStatusKey(tradeTxData) === SUBMITTED_STATUS, + statusKey === SUBMITTED_STATUS, true, tradeTxData?.submittedTime, usedGasPrice, From a284660986f3b38edc623e8d36917dbd05a76f10 Mon Sep 17 00:00:00 2001 From: MetaMask Bot Date: Mon, 19 Oct 2020 21:32:37 +0000 Subject: [PATCH 15/18] Version v8.1.2 --- CHANGELOG.md | 2 ++ app/manifest/_base.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a42139678..8c62aec15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Develop Branch +## 8.1.2 Mon Oct 19 2020 + ## 8.1.1 Tue Oct 13 2020 - [#9586](https://github.com/MetaMask/metamask-extension/pull/9586): Prevent build quote crash when swapping from non-tracked token with balance (#9586) - [#9592](https://github.com/MetaMask/metamask-extension/pull/9592): Remove commitment to maintain a public metrics dashboard (#9592) diff --git a/app/manifest/_base.json b/app/manifest/_base.json index 09bb58e6d..f32d4e9bb 100644 --- a/app/manifest/_base.json +++ b/app/manifest/_base.json @@ -68,6 +68,6 @@ "notifications" ], "short_name": "__MSG_appName__", - "version": "8.1.1", + "version": "8.1.2", "web_accessible_resources": ["inpage.js", "phishing.html"] } From d684c1b2e329830195c65bd5a5d9e35ecb2aa9cf Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Mon, 19 Oct 2020 19:40:03 -0230 Subject: [PATCH 16/18] Update changelog for v8.1.2 (#9645) All user-facing changes since v8.1.1 have been included. --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c62aec15..c2d1ff61c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ ## Current Develop Branch ## 8.1.2 Mon Oct 19 2020 +- [#9608](https://github.com/MetaMask/metamask-extension/pull/9608): Ensure QR code scanner works +- [#9624](https://github.com/MetaMask/metamask-extension/pull/9624): Help users avoid insufficient gas prices in swaps +- [#9614](https://github.com/MetaMask/metamask-extension/pull/9614): Update swaps network fee tooltip +- [#9623](https://github.com/MetaMask/metamask-extension/pull/9623): Prevent reducing the gas limit for swaps +- [#9630](https://github.com/MetaMask/metamask-extension/pull/9630): Fix UI crash when trying to render estimated time remaining of non-submitted transaction +- [#9633](https://github.com/MetaMask/metamask-extension/pull/9633): Update View Quote page to better represent the MetaMask fee ## 8.1.1 Tue Oct 13 2020 - [#9586](https://github.com/MetaMask/metamask-extension/pull/9586): Prevent build quote crash when swapping from non-tracked token with balance (#9586) From ff1e134ac9cc908027cd94ed0571aec44f89ae72 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Mon, 19 Oct 2020 20:58:48 -0230 Subject: [PATCH 17/18] Remove duplicate percent sign from MetaMask fee (#9647) The MetaMask fee is shown with two percent signs on the view quote page, because the percent sign is embedded in the fee amount as well as in the localized message. The fee amount used now comes from the API, and does not have a percent sign. The percent sign is now only in the localized message. This allows for different locales to display the percentage differently. The old hard-coded value with a percent sign embedded has been removed, as it is no longer used anywhere. --- ui/app/pages/swaps/swaps.util.js | 2 -- ui/app/pages/swaps/view-quote/view-quote.js | 4 +++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/app/pages/swaps/swaps.util.js b/ui/app/pages/swaps/swaps.util.js index 92d20556e..d1805c1cb 100644 --- a/ui/app/pages/swaps/swaps.util.js +++ b/ui/app/pages/swaps/swaps.util.js @@ -315,7 +315,6 @@ export function quotesToRenderableData (quotes, gasPrice, conversionRate, curren conversionRate, ) - const metaMaskFee = `0.875%` const slippageMultiplier = (new BigNumber(100 - slippage)).div(100) const minimumAmountReceived = (new BigNumber(destinationValue)).times(slippageMultiplier).toFixed(6) @@ -348,7 +347,6 @@ export function quotesToRenderableData (quotes, gasPrice, conversionRate, curren destinationTokenValue: formatSwapsValueForDisplay(destinationValue), isBestQuote: quote.isBestQuote, liquiditySourceKey, - metaMaskFee, feeInEth, detailedNetworkFees: `${feeInEth} (${feeInFiat})`, networkFees: feeInFiat, diff --git a/ui/app/pages/swaps/view-quote/view-quote.js b/ui/app/pages/swaps/view-quote/view-quote.js index 3ebb16803..da08f7615 100644 --- a/ui/app/pages/swaps/view-quote/view-quote.js +++ b/ui/app/pages/swaps/view-quote/view-quote.js @@ -22,6 +22,7 @@ import { getBalanceError, getCustomSwapsGas, getDestinationTokenInfo, + getMetaMaskFeeAmount, getSwapsTradeTxParams, getTopQuote, navigateBackToBuildQuote, @@ -200,7 +201,6 @@ export default function ViewQuote () { sourceTokenDecimals, sourceTokenSymbol, sourceTokenValue, - metaMaskFee, } = renderableDataForUsedQuote const { feeInFiat, feeInEth } = getRenderableGasFeesForQuote( @@ -341,6 +341,8 @@ export default function ViewQuote () { } }, [sourceTokenSymbol, sourceTokenValue, destinationTokenSymbol, destinationTokenValue, fetchParams, topQuote, numberOfQuotes, feeInFiat, bestQuoteReviewedEvent, anonymousBestQuoteReviewedEvent]) + const metaMaskFee = useSelector(getMetaMaskFeeAmount) + const onFeeCardTokenApprovalClick = () => { anonymousEditSpendLimitOpened() editSpendLimitOpened() From aa554f51397b77d5a72c67d15fd7822089b31e9c Mon Sep 17 00:00:00 2001 From: Erik Marks <25517051+rekmarks@users.noreply.github.com> Date: Mon, 19 Oct 2020 16:29:31 -0700 Subject: [PATCH 18/18] Fix estimated network fee line split (#9648) --- ui/app/pages/swaps/fee-card/index.scss | 5 +---- ui/app/pages/swaps/swaps.util.js | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/ui/app/pages/swaps/fee-card/index.scss b/ui/app/pages/swaps/fee-card/index.scss index 262b7a5eb..0a86f6ab7 100644 --- a/ui/app/pages/swaps/fee-card/index.scss +++ b/ui/app/pages/swaps/fee-card/index.scss @@ -106,11 +106,8 @@ color: $Grey-500; } + &__row-header-secondary, &__row-header-secondary--bold { - margin-right: 16px; - } - - &__row-header-secondary { margin-right: 12px; } diff --git a/ui/app/pages/swaps/swaps.util.js b/ui/app/pages/swaps/swaps.util.js index d1805c1cb..32974d5b6 100644 --- a/ui/app/pages/swaps/swaps.util.js +++ b/ui/app/pages/swaps/swaps.util.js @@ -275,7 +275,7 @@ export function getRenderableGasFeesForQuote (tradeGas, approveGas, gasPrice, cu const ethFee = getValueFromWeiHex({ value: gasTotalInWeiHex, toDenomination: 'ETH', - numberOfDecimals: 6, + numberOfDecimals: 5, }) const rawNetworkFees = getValueFromWeiHex({ value: gasTotalInWeiHex,