diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index f98bd83a4..69fd51a1d 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -684,6 +684,9 @@ "estimatedProcessingTimes": { "message": "Estimated Processing Times" }, + "ethGasPriceFetchWarning": { + "message": "Backup gas price is provided as the main gas estimation service is unavailable right now." + }, "eth_accounts": { "message": "View the addresses of your permitted accounts (required)", "description": "The description for the `eth_accounts` permission" @@ -780,6 +783,9 @@ "gasPriceExtremelyLow": { "message": "Gas Price Extremely Low" }, + "gasPriceFetchFailed": { + "message": "Gas price estimation failed due to network error." + }, "gasPriceInfoTooltipContent": { "message": "Gas price specifies the amount of Ether you are willing to pay for each unit of gas." }, diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js index 9009f9feb..e47bfaf78 100644 --- a/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js +++ b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js @@ -22,6 +22,7 @@ export default class ConfirmPageContainerContent extends Component { titleComponent: PropTypes.node, warning: PropTypes.string, origin: PropTypes.string.isRequired, + ethGasPriceWarning: PropTypes.string, // Footer onCancelAll: PropTypes.func, onCancel: PropTypes.func, @@ -81,11 +82,15 @@ export default class ConfirmPageContainerContent extends Component { unapprovedTxCount, rejectNText, origin, + ethGasPriceWarning, } = this.props; return (
{warning && } + {ethGasPriceWarning && ( + + )} )} {contentComponent && ( 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 f57cce92d..ea59b7882 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 @@ -123,19 +123,25 @@ export default class GasModalPageContainer extends Component { infoRowProps: { newTotalFiat, newTotalEth, sendAmount, transactionFee }, } = this.props; - let tabsToRender = [ - { - name: this.context.t('basic'), - content: this.renderBasicTabContent(gasPriceButtonGroupProps), - }, - { - name: this.context.t('advanced'), - content: this.renderAdvancedTabContent(), - }, - ]; - + let tabsToRender; if (hideBasic) { - tabsToRender = tabsToRender.slice(1); + tabsToRender = [ + { + name: this.context.t('advanced'), + content: this.renderAdvancedTabContent(), + }, + ]; + } else { + tabsToRender = [ + { + name: this.context.t('basic'), + content: this.renderBasicTabContent(gasPriceButtonGroupProps), + }, + { + name: this.context.t('advanced'), + content: this.renderAdvancedTabContent(), + }, + ]; } return ( diff --git a/ui/app/ducks/gas/gas-duck.test.js b/ui/app/ducks/gas/gas-duck.test.js index eab7a74b6..e4e51beab 100644 --- a/ui/app/ducks/gas/gas-duck.test.js +++ b/ui/app/ducks/gas/gas-duck.test.js @@ -3,8 +3,7 @@ import sinon from 'sinon'; import BN from 'bn.js'; import GasReducer, { - basicGasEstimatesLoadingStarted, - basicGasEstimatesLoadingFinished, + setBasicEstimateStatus, setBasicGasEstimateData, setCustomGasPrice, setCustomGasLimit, @@ -49,7 +48,8 @@ describe('Gas Duck', () => { fast: null, safeLow: null, }, - basicEstimateIsLoading: true, + basicEstimateStatus: 'LOADING', + estimateSource: '', }; const providerState = { @@ -61,14 +61,12 @@ describe('Gas Duck', () => { type: 'mainnet', }; - const BASIC_GAS_ESTIMATE_LOADING_FINISHED = - 'metamask/gas/BASIC_GAS_ESTIMATE_LOADING_FINISHED'; - const BASIC_GAS_ESTIMATE_LOADING_STARTED = - 'metamask/gas/BASIC_GAS_ESTIMATE_LOADING_STARTED'; + const BASIC_GAS_ESTIMATE_STATUS = 'metamask/gas/BASIC_GAS_ESTIMATE_STATUS'; const SET_BASIC_GAS_ESTIMATE_DATA = 'metamask/gas/SET_BASIC_GAS_ESTIMATE_DATA'; const SET_CUSTOM_GAS_LIMIT = 'metamask/gas/SET_CUSTOM_GAS_LIMIT'; const SET_CUSTOM_GAS_PRICE = 'metamask/gas/SET_CUSTOM_GAS_PRICE'; + const SET_ESTIMATE_SOURCE = 'metamask/gas/SET_ESTIMATE_SOURCE'; describe('GasReducer()', () => { it('should initialize state', () => { @@ -84,16 +82,13 @@ describe('Gas Duck', () => { ).toStrictEqual(mockState); }); - it('should set basicEstimateIsLoading to true when receiving a BASIC_GAS_ESTIMATE_LOADING_STARTED action', () => { + it('should set basicEstimateStatus to LOADING when receiving a BASIC_GAS_ESTIMATE_STATUS action with value LOADING', () => { expect( - GasReducer(mockState, { type: BASIC_GAS_ESTIMATE_LOADING_STARTED }), - ).toStrictEqual({ basicEstimateIsLoading: true, ...mockState }); - }); - - it('should set basicEstimateIsLoading to false when receiving a BASIC_GAS_ESTIMATE_LOADING_FINISHED action', () => { - expect( - GasReducer(mockState, { type: BASIC_GAS_ESTIMATE_LOADING_FINISHED }), - ).toStrictEqual({ basicEstimateIsLoading: false, ...mockState }); + GasReducer(mockState, { + type: BASIC_GAS_ESTIMATE_STATUS, + value: 'LOADING', + }), + ).toStrictEqual({ basicEstimateStatus: 'LOADING', ...mockState }); }); it('should set basicEstimates when receiving a SET_BASIC_GAS_ESTIMATE_DATA action', () => { @@ -127,18 +122,17 @@ describe('Gas Duck', () => { }); }); - describe('basicGasEstimatesLoadingStarted', () => { - it('should create the correct action', () => { - expect(basicGasEstimatesLoadingStarted()).toStrictEqual({ - type: BASIC_GAS_ESTIMATE_LOADING_STARTED, - }); - }); + it('should set estimateSource to Metaswaps when receiving a SET_ESTIMATE_SOURCE action with value Metaswaps', () => { + expect( + GasReducer(mockState, { type: SET_ESTIMATE_SOURCE, value: 'Metaswaps' }), + ).toStrictEqual({ estimateSource: 'Metaswaps', ...mockState }); }); - describe('basicGasEstimatesLoadingFinished', () => { + describe('basicEstimateStatus', () => { it('should create the correct action', () => { - expect(basicGasEstimatesLoadingFinished()).toStrictEqual({ - type: BASIC_GAS_ESTIMATE_LOADING_FINISHED, + expect(setBasicEstimateStatus('LOADING')).toStrictEqual({ + type: BASIC_GAS_ESTIMATE_STATUS, + value: 'LOADING', }); }); }); @@ -158,7 +152,7 @@ describe('Gas Duck', () => { })); expect(mockDistpatch.getCall(0).args).toStrictEqual([ - { type: BASIC_GAS_ESTIMATE_LOADING_STARTED }, + { type: 'metamask/gas/BASIC_GAS_ESTIMATE_STATUS', value: 'LOADING' }, ]); expect( @@ -168,7 +162,11 @@ describe('Gas Duck', () => { ).toStrictEqual(true); expect(mockDistpatch.getCall(2).args).toStrictEqual([ - { type: BASIC_GAS_ESTIMATE_LOADING_FINISHED }, + { type: 'metamask/gas/SET_ESTIMATE_SOURCE', value: 'MetaSwaps' }, + ]); + + expect(mockDistpatch.getCall(4).args).toStrictEqual([ + { type: 'metamask/gas/BASIC_GAS_ESTIMATE_STATUS', value: 'READY' }, ]); }); @@ -190,9 +188,12 @@ describe('Gas Duck', () => { metamask: { provider: { ...providerStateForTestNetwork } }, })); expect(mockDistpatch.getCall(0).args).toStrictEqual([ - { type: BASIC_GAS_ESTIMATE_LOADING_STARTED }, + { type: 'metamask/gas/BASIC_GAS_ESTIMATE_STATUS', value: 'LOADING' }, ]); expect(mockDistpatch.getCall(1).args).toStrictEqual([ + { type: 'metamask/gas/SET_ESTIMATE_SOURCE', value: 'eth_gasprice' }, + ]); + expect(mockDistpatch.getCall(2).args).toStrictEqual([ { type: SET_BASIC_GAS_ESTIMATE_DATA, value: { @@ -200,8 +201,8 @@ describe('Gas Duck', () => { }, }, ]); - expect(mockDistpatch.getCall(2).args).toStrictEqual([ - { type: BASIC_GAS_ESTIMATE_LOADING_FINISHED }, + expect(mockDistpatch.getCall(3).args).toStrictEqual([ + { type: 'metamask/gas/BASIC_GAS_ESTIMATE_STATUS', value: 'READY' }, ]); }); }); diff --git a/ui/app/ducks/gas/gas.duck.js b/ui/app/ducks/gas/gas.duck.js index 5785b2cf3..48d277443 100644 --- a/ui/app/ducks/gas/gas.duck.js +++ b/ui/app/ducks/gas/gas.duck.js @@ -8,15 +8,24 @@ import { import { getIsMainnet, getCurrentChainId } from '../../selectors'; import fetchWithCache from '../../helpers/utils/fetch-with-cache'; +const BASIC_ESTIMATE_STATES = { + LOADING: 'LOADING', + FAILED: 'FAILED', + READY: 'READY', +}; + +const GAS_SOURCE = { + METASWAPS: 'MetaSwaps', + ETHGASPRICE: 'eth_gasprice', +}; + // Actions -const BASIC_GAS_ESTIMATE_LOADING_FINISHED = - 'metamask/gas/BASIC_GAS_ESTIMATE_LOADING_FINISHED'; -const BASIC_GAS_ESTIMATE_LOADING_STARTED = - 'metamask/gas/BASIC_GAS_ESTIMATE_LOADING_STARTED'; +const BASIC_GAS_ESTIMATE_STATUS = 'metamask/gas/BASIC_GAS_ESTIMATE_STATUS'; const RESET_CUSTOM_DATA = 'metamask/gas/RESET_CUSTOM_DATA'; const SET_BASIC_GAS_ESTIMATE_DATA = 'metamask/gas/SET_BASIC_GAS_ESTIMATE_DATA'; const SET_CUSTOM_GAS_LIMIT = 'metamask/gas/SET_CUSTOM_GAS_LIMIT'; const SET_CUSTOM_GAS_PRICE = 'metamask/gas/SET_CUSTOM_GAS_PRICE'; +const SET_ESTIMATE_SOURCE = 'metamask/gas/SET_ESTIMATE_SOURCE'; const initState = { customData: { @@ -28,21 +37,17 @@ const initState = { average: null, fast: null, }, - basicEstimateIsLoading: true, + basicEstimateStatus: BASIC_ESTIMATE_STATES.LOADING, + estimateSource: '', }; // Reducer export default function reducer(state = initState, action) { switch (action.type) { - case BASIC_GAS_ESTIMATE_LOADING_STARTED: + case BASIC_GAS_ESTIMATE_STATUS: return { ...state, - basicEstimateIsLoading: true, - }; - case BASIC_GAS_ESTIMATE_LOADING_FINISHED: - return { - ...state, - basicEstimateIsLoading: false, + basicEstimateStatus: action.value, }; case SET_BASIC_GAS_ESTIMATE_DATA: return { @@ -70,21 +75,21 @@ export default function reducer(state = initState, action) { ...state, customData: cloneDeep(initState.customData), }; + case SET_ESTIMATE_SOURCE: + return { + ...state, + estimateSource: action.value, + }; default: return state; } } // Action Creators -export function basicGasEstimatesLoadingStarted() { +export function setBasicEstimateStatus(status) { return { - type: BASIC_GAS_ESTIMATE_LOADING_STARTED, - }; -} - -export function basicGasEstimatesLoadingFinished() { - return { - type: BASIC_GAS_ESTIMATE_LOADING_FINISHED, + type: BASIC_GAS_ESTIMATE_STATUS, + value: status, }; } @@ -106,18 +111,26 @@ export function fetchBasicGasEstimates() { return async (dispatch, getState) => { const isMainnet = getIsMainnet(getState()); - dispatch(basicGasEstimatesLoadingStarted()); - + dispatch(setBasicEstimateStatus(BASIC_ESTIMATE_STATES.LOADING)); let basicEstimates; - if (isMainnet || process.env.IN_TEST) { - basicEstimates = await fetchExternalBasicGasEstimates(); - } else { - basicEstimates = await fetchEthGasPriceEstimates(getState()); + try { + dispatch(setEstimateSource(GAS_SOURCE.ETHGASPRICE)); + if (isMainnet || process.env.IN_TEST) { + try { + basicEstimates = await fetchExternalBasicGasEstimates(); + dispatch(setEstimateSource(GAS_SOURCE.METASWAPS)); + } catch (error) { + basicEstimates = await fetchEthGasPriceEstimates(getState()); + } + } else { + basicEstimates = await fetchEthGasPriceEstimates(getState()); + } + dispatch(setBasicGasEstimateData(basicEstimates)); + dispatch(setBasicEstimateStatus(BASIC_ESTIMATE_STATES.READY)); + } catch (error) { + dispatch(setBasicEstimateStatus(BASIC_ESTIMATE_STATES.FAILED)); } - dispatch(setBasicGasEstimateData(basicEstimates)); - dispatch(basicGasEstimatesLoadingFinished()); - return basicEstimates; }; } @@ -211,3 +224,10 @@ export function setCustomGasLimit(newLimit) { export function resetCustomData() { return { type: RESET_CUSTOM_DATA }; } + +export function setEstimateSource(estimateSource) { + return { + type: SET_ESTIMATE_SOURCE, + value: estimateSource, + }; +} diff --git a/ui/app/helpers/constants/error-keys.js b/ui/app/helpers/constants/error-keys.js index 85ce13d7d..bfdb7474f 100644 --- a/ui/app/helpers/constants/error-keys.js +++ b/ui/app/helpers/constants/error-keys.js @@ -2,3 +2,6 @@ export const INSUFFICIENT_FUNDS_ERROR_KEY = 'insufficientFunds'; export const GAS_LIMIT_TOO_LOW_ERROR_KEY = 'gasLimitTooLow'; export const TRANSACTION_ERROR_KEY = 'transactionError'; export const TRANSACTION_NO_CONTRACT_ERROR_KEY = 'transactionErrorNoContract'; +export const ETH_GAS_PRICE_FETCH_WARNING_KEY = 'ethGasPriceFetchWarning'; +export const GAS_PRICE_FETCH_FAILURE_ERROR_KEY = 'gasPriceFetchFailed'; +export const GAS_PRICE_EXCESSIVE_ERROR_KEY = 'gasPriceExcessive'; diff --git a/ui/app/pages/confirm-approve/confirm-approve.js b/ui/app/pages/confirm-approve/confirm-approve.js index 9eb51bd2a..b7731e752 100644 --- a/ui/app/pages/confirm-approve/confirm-approve.js +++ b/ui/app/pages/confirm-approve/confirm-approve.js @@ -24,6 +24,8 @@ import { getUseNonceField, getCustomNonceValue, getNextSuggestedNonce, + getNoGasPriceFetched, + getIsEthGasPriceFetched, } from '../../selectors'; import { currentNetworkTxListSelector } from '../../selectors/transactions'; import Loading from '../../components/ui/loading-screen'; @@ -116,6 +118,8 @@ export default function ConfirmApprove() { const customData = customPermissionAmount ? getCustomTxParamsData(data, { customPermissionAmount, decimals }) : null; + const isEthGasPrice = useSelector(getIsEthGasPriceFetched); + const noGasPrice = useSelector(getNoGasPriceFetched); return tokenSymbol === undefined ? ( @@ -136,7 +140,13 @@ export default function ConfirmApprove() { tokenSymbol={tokenSymbol} tokenBalance={tokenBalance} showCustomizeGasModal={() => - dispatch(showModal({ name: 'CUSTOMIZE_GAS', txData })) + dispatch( + showModal({ + name: 'CUSTOMIZE_GAS', + txData, + hideBasic: isEthGasPrice || noGasPrice, + }), + ) } showEditApprovalPermissionModal={({ /* eslint-disable no-shadow */ 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 54646ac64..9a2cab98c 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 @@ -12,6 +12,8 @@ import { INSUFFICIENT_FUNDS_ERROR_KEY, TRANSACTION_ERROR_KEY, GAS_LIMIT_TOO_LOW_ERROR_KEY, + ETH_GAS_PRICE_FETCH_WARNING_KEY, + GAS_PRICE_FETCH_FAILURE_ERROR_KEY, } from '../../helpers/constants/error-keys'; import UserPreferencedCurrencyDisplay from '../../components/app/user-preferenced-currency-display'; import { PRIMARY, SECONDARY } from '../../helpers/constants/common'; @@ -23,6 +25,7 @@ import { TRANSACTION_STATUSES, } from '../../../../shared/constants/transaction'; import { getTransactionTypeTitle } from '../../helpers/utils/transactions.util'; +import ErrorMessage from '../../components/ui/error-message'; export default class ConfirmTransactionBase extends Component { static contextTypes = { @@ -95,12 +98,15 @@ export default class ConfirmTransactionBase extends Component { showAccountInHeader: PropTypes.bool, mostRecentOverviewPage: PropTypes.string.isRequired, isMainnet: PropTypes.bool, + isEthGasPrice: PropTypes.bool, + noGasPrice: PropTypes.bool, }; state = { submitting: false, submitError: null, submitWarning: '', + ethGasPriceWarning: '', }; componentDidUpdate(prevProps) { @@ -114,12 +120,14 @@ export default class ConfirmTransactionBase extends Component { customNonceValue, toAddress, tryReverseResolveAddress, + isEthGasPrice, } = this.props; const { customNonceValue: prevCustomNonceValue, nextNonce: prevNextNonce, toAddress: prevToAddress, transactionStatus: prevTxStatus, + isEthGasPrice: prevIsEthGasPrice, } = prevProps; const statusUpdated = transactionStatus !== prevTxStatus; const txDroppedOrConfirmed = @@ -151,6 +159,18 @@ export default class ConfirmTransactionBase extends Component { if (toAddress && toAddress !== prevToAddress) { tryReverseResolveAddress(toAddress); } + + if (isEthGasPrice !== prevIsEthGasPrice) { + if (isEthGasPrice) { + this.setState({ + ethGasPriceWarning: this.context.t(ETH_GAS_PRICE_FETCH_WARNING_KEY), + }); + } else { + this.setState({ + ethGasPriceWarning: '', + }); + } + } } getErrorKey() { @@ -160,6 +180,7 @@ export default class ConfirmTransactionBase extends Component { hexTransactionFee, txData: { simulationFails, txParams: { value: amount } = {} } = {}, customGas, + noGasPrice, } = this.props; const insufficientBalance = @@ -194,6 +215,13 @@ export default class ConfirmTransactionBase extends Component { }; } + if (noGasPrice) { + return { + valid: false, + errorKey: GAS_PRICE_FETCH_FAILURE_ERROR_KEY, + }; + } + return { valid: true, }; @@ -243,9 +271,12 @@ export default class ConfirmTransactionBase extends Component { nextNonce, getNextNonce, isMainnet, + isEthGasPrice, + noGasPrice, } = this.props; const notMainnetOrTest = !(isMainnet || process.env.IN_TEST); + const gasPriceFetchFailure = isEthGasPrice || noGasPrice; return (
@@ -253,18 +284,26 @@ export default class ConfirmTransactionBase extends Component { this.handleEditGas() } - onHeaderClick={notMainnetOrTest ? null : () => this.handleEditGas()} secondaryText={ hideFiatConversion ? this.context.t('noConversionRateAvailable') : '' } /> - {advancedInlineGasShown || notMainnetOrTest ? ( + {advancedInlineGasShown || + notMainnetOrTest || + gasPriceFetchFailure ? ( updateGasAndCalculate({ ...customGas, gasPrice: newGasPrice }) @@ -279,6 +318,11 @@ export default class ConfirmTransactionBase extends Component { isSpeedUp={false} /> ) : null} + {noGasPrice ? ( +
+ +
+ ) : null}
this.handleSubmit()} hideSenderToRecipient={hideSenderToRecipient} origin={txData.origin} + ethGasPriceWarning={ethGasPriceWarning} /> ); } diff --git a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js index 7a761bd9b..bf32a6697 100644 --- a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js +++ b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js @@ -37,6 +37,8 @@ import { getUseNonceField, getPreferences, transactionFeeSelector, + getNoGasPriceFetched, + getIsEthGasPriceFetched, } from '../../selectors'; import { getMostRecentOverviewPage } from '../../ducks/history/history'; import { transactionMatchesNetwork } from '../../../../shared/modules/transaction.utils'; @@ -150,6 +152,8 @@ const mapStateToProps = (state, ownProps) => { }; } customNonceValue = getCustomNonceValue(state); + const isEthGasPrice = getIsEthGasPriceFetched(state); + const noGasPrice = getNoGasPriceFetched(state); return { balance, @@ -189,6 +193,8 @@ const mapStateToProps = (state, ownProps) => { nextNonce, mostRecentOverviewPage: getMostRecentOverviewPage(state), isMainnet, + isEthGasPrice, + noGasPrice, }; }; @@ -207,7 +213,12 @@ export const mapDispatchToProps = (dispatch) => { }, showCustomizeGasModal: ({ txData, onSubmit, validate }) => { return dispatch( - showModal({ name: 'CUSTOMIZE_GAS', txData, onSubmit, validate }), + showModal({ + name: 'CUSTOMIZE_GAS', + txData, + onSubmit, + validate, + }), ); }, updateGasAndCalculate: (updatedTx) => { @@ -278,6 +289,7 @@ const getValidateEditGas = ({ balance, conversionRate, txData }) => { const mergeProps = (stateProps, dispatchProps, ownProps) => { const { balance, conversionRate, txData, unapprovedTxs } = stateProps; + const { cancelAllTransactions: dispatchCancelAllTransactions, showCustomizeGasModal: dispatchShowCustomizeGasModal, diff --git a/ui/app/pages/send/send-content/send-content.component.js b/ui/app/pages/send/send-content/send-content.component.js index 6f4fd7924..6f1a82b92 100644 --- a/ui/app/pages/send/send-content/send-content.component.js +++ b/ui/app/pages/send/send-content/send-content.component.js @@ -2,6 +2,11 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import PageContainerContent from '../../../components/ui/page-container/page-container-content.component'; import Dialog from '../../../components/ui/dialog'; +import { + ETH_GAS_PRICE_FETCH_WARNING_KEY, + GAS_PRICE_FETCH_FAILURE_ERROR_KEY, + GAS_PRICE_EXCESSIVE_ERROR_KEY, +} from '../../../helpers/constants/error-keys'; import SendAmountRow from './send-amount-row'; import SendGasRow from './send-gas-row'; import SendHexDataRow from './send-hex-data-row'; @@ -21,16 +26,30 @@ export default class SendContent extends Component { warning: PropTypes.string, error: PropTypes.string, gasIsExcessive: PropTypes.bool.isRequired, + isEthGasPrice: PropTypes.bool, + noGasPrice: PropTypes.bool, }; updateGas = (updateData) => this.props.updateGas(updateData); render() { - const { warning, error, gasIsExcessive } = this.props; + const { + warning, + error, + gasIsExcessive, + isEthGasPrice, + noGasPrice, + } = this.props; + + let gasError; + if (gasIsExcessive) gasError = GAS_PRICE_EXCESSIVE_ERROR_KEY; + else if (noGasPrice) gasError = GAS_PRICE_FETCH_FAILURE_ERROR_KEY; + return (
- {gasIsExcessive && this.renderError(true)} + {gasError && this.renderError(gasError)} + {isEthGasPrice && this.renderWarning(ETH_GAS_PRICE_FETCH_WARNING_KEY)} {error && this.renderError()} {warning && this.renderWarning()} {this.maybeRenderAddContact()} @@ -68,24 +87,22 @@ export default class SendContent extends Component { ); } - renderWarning() { + renderWarning(gasWarning = '') { const { t } = this.context; const { warning } = this.props; - return ( - {t(warning)} + {gasWarning === '' ? t(warning) : t(gasWarning)} ); } - renderError(gasError = false) { + renderError(gasError = '') { const { t } = this.context; const { error } = this.props; - return ( - {gasError ? t('gasPriceExcessive') : t(error)} + {gasError === '' ? t(error) : t(gasError)} ); } diff --git a/ui/app/pages/send/send-content/send-content.container.js b/ui/app/pages/send/send-content/send-content.container.js index 42e115b94..3c99b3237 100644 --- a/ui/app/pages/send/send-content/send-content.container.js +++ b/ui/app/pages/send/send-content/send-content.container.js @@ -3,6 +3,8 @@ import { getSendTo, accountsWithSendEtherInfoSelector, getAddressBookEntry, + getIsEthGasPriceFetched, + getNoGasPriceFetched, } from '../../../selectors'; import * as actions from '../../../store/actions'; @@ -19,6 +21,8 @@ function mapStateToProps(state) { ), contact: getAddressBookEntry(state, to), to, + isEthGasPrice: getIsEthGasPriceFetched(state), + noGasPrice: getNoGasPriceFetched(state), }; } diff --git a/ui/app/pages/send/send-content/send-gas-row/send-gas-row.component.js b/ui/app/pages/send/send-content/send-gas-row/send-gas-row.component.js index cf74ee0f3..9cbe21629 100644 --- a/ui/app/pages/send/send-content/send-gas-row/send-gas-row.component.js +++ b/ui/app/pages/send/send-content/send-gas-row/send-gas-row.component.js @@ -26,6 +26,8 @@ export default class SendGasRow extends Component { gasLimit: PropTypes.string, insufficientBalance: PropTypes.bool, isMainnet: PropTypes.bool, + isEthGasPrice: PropTypes.bool, + noGasPrice: PropTypes.bool, }; static contextTypes = { @@ -35,11 +37,19 @@ export default class SendGasRow extends Component { renderAdvancedOptionsButton() { const { metricsEvent } = this.context; - const { showCustomizeGasModal, isMainnet } = this.props; + const { + showCustomizeGasModal, + isMainnet, + isEthGasPrice, + noGasPrice, + } = this.props; // Tests should behave in same way as mainnet, but are using Localhost if (!isMainnet && !process.env.IN_TEST) { return null; } + if (isEthGasPrice || noGasPrice) { + return null; + } return (
@@ -148,7 +161,11 @@ export default class SendGasRow extends Component {
); // Tests should behave in same way as mainnet, but are using Localhost - if (advancedInlineGasShown || (!isMainnet && !process.env.IN_TEST)) { + if ( + advancedInlineGasShown || + (!isMainnet && !process.env.IN_TEST) || + gasPriceFetchFailure + ) { return advancedGasInputs; } else if (gasButtonGroupShown) { return gasPriceButtonGroup; diff --git a/ui/app/pages/send/send-content/send-gas-row/send-gas-row.container.js b/ui/app/pages/send/send-content/send-gas-row/send-gas-row.container.js index 210819f07..32b0529c4 100644 --- a/ui/app/pages/send/send-content/send-gas-row/send-gas-row.container.js +++ b/ui/app/pages/send/send-content/send-gas-row/send-gas-row.container.js @@ -18,6 +18,8 @@ import { getRenderableEstimateDataForSmallButtonsFromGWEI, getDefaultActiveButtonIndex, getIsMainnet, + getIsEthGasPriceFetched, + getNoGasPriceFetched, } from '../../../../selectors'; import { isBalanceSufficient, calcGasTotal } from '../../send.utils'; import { calcMaxAmount } from '../send-amount-row/amount-max-button/amount-max-button.utils'; @@ -64,6 +66,8 @@ function mapStateToProps(state) { balance, conversionRate, }); + const isEthGasPrice = getIsEthGasPriceFetched(state); + const noGasPrice = getNoGasPriceFetched(state); return { balance: getSendFromBalance(state), @@ -85,6 +89,8 @@ function mapStateToProps(state) { sendToken: getSendToken(state), tokenBalance: getTokenBalance(state), isMainnet: getIsMainnet(state), + isEthGasPrice, + noGasPrice, }; } diff --git a/ui/app/pages/send/send-footer/send-footer.component.js b/ui/app/pages/send/send-footer/send-footer.component.js index 5956d9517..ef18f48b1 100644 --- a/ui/app/pages/send/send-footer/send-footer.component.js +++ b/ui/app/pages/send/send-footer/send-footer.component.js @@ -27,6 +27,7 @@ export default class SendFooter extends Component { gasEstimateType: PropTypes.string, gasIsLoading: PropTypes.bool, mostRecentOverviewPage: PropTypes.string.isRequired, + noGasPrice: PropTypes.bool, }; static contextTypes = { @@ -109,6 +110,7 @@ export default class SendFooter extends Component { to, gasLimit, gasIsLoading, + noGasPrice, } = this.props; const missingTokenBalance = sendToken && !tokenBalance; const gasLimitTooLow = gasLimit < 5208; // 5208 is hex value of 21000, minimum gas limit @@ -118,7 +120,8 @@ export default class SendFooter extends Component { missingTokenBalance || !(data || to) || gasLimitTooLow || - gasIsLoading; + gasIsLoading || + noGasPrice; return shouldBeDisabled; } diff --git a/ui/app/pages/send/send-footer/send-footer.component.test.js b/ui/app/pages/send/send-footer/send-footer.component.test.js index cdcc06308..900c26b2a 100644 --- a/ui/app/pages/send/send-footer/send-footer.component.test.js +++ b/ui/app/pages/send/send-footer/send-footer.component.test.js @@ -49,6 +49,7 @@ describe('SendFooter Component', () => { update={propsMethodSpies.update} sendErrors={{}} mostRecentOverviewPage="mostRecentOverviewPage" + noGasPrice={false} />, { context: { t: (str) => str, metricsEvent: () => ({}) } }, ); diff --git a/ui/app/pages/send/send-footer/send-footer.container.js b/ui/app/pages/send/send-footer/send-footer.container.js index ff7b475bc..d68d16b60 100644 --- a/ui/app/pages/send/send-footer/send-footer.container.js +++ b/ui/app/pages/send/send-footer/send-footer.container.js @@ -24,6 +24,7 @@ import { getGasIsLoading, getRenderableEstimateDataForSmallButtonsFromGWEI, getDefaultActiveButtonIndex, + getNoGasPriceFetched, } from '../../../selectors'; import { getMostRecentOverviewPage } from '../../../ducks/history/history'; import { addHexPrefix } from '../../../../../app/scripts/lib/util'; @@ -67,6 +68,7 @@ function mapStateToProps(state) { gasEstimateType, gasIsLoading: getGasIsLoading(state), mostRecentOverviewPage: getMostRecentOverviewPage(state), + noGasPrice: getNoGasPriceFetched(state), }; } diff --git a/ui/app/selectors/custom-gas.js b/ui/app/selectors/custom-gas.js index c84c48c71..5f01e8d08 100644 --- a/ui/app/selectors/custom-gas.js +++ b/ui/app/selectors/custom-gas.js @@ -27,12 +27,14 @@ export function getCustomGasPrice(state) { } export function getBasicGasEstimateLoadingStatus(state) { - return state.gas.basicEstimateIsLoading; + return state.gas.basicEstimateStatus === 'LOADING'; } export function getAveragePriceEstimateInHexWEI(state) { - const averagePriceEstimate = state.gas.basicEstimates.average; - return getGasPriceInHexWei(averagePriceEstimate || '0x0'); + const averagePriceEstimate = state.gas.basicEstimates + ? state.gas.basicEstimates.average + : '0x0'; + return getGasPriceInHexWei(averagePriceEstimate); } export function getFastPriceEstimateInHexWEI(state) { @@ -355,3 +357,17 @@ export function getRenderableEstimateDataForSmallButtonsFromGWEI(state) { }, ]; } + +export function getIsEthGasPriceFetched(state) { + const gasState = state.gas; + return Boolean( + gasState.estimateSource === 'eth_gasprice' && + gasState.basicEstimateStatus === 'READY' && + getIsMainnet(state), + ); +} + +export function getNoGasPriceFetched(state) { + const gasState = state.gas; + return Boolean(gasState.basicEstimateStatus === 'FAILED'); +}