1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-10-22 19:26:13 +02:00

Handling gas price fetch failure (#10767)

This commit is contained in:
Niranjana Binoy 2021-04-28 14:02:01 -04:00 committed by GitHub
parent b73f543b23
commit f1fc51667a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 274 additions and 94 deletions

View File

@ -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."
},

View File

@ -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 (
<div className="confirm-page-container-content">
{warning && <ConfirmPageContainerWarning warning={warning} />}
{ethGasPriceWarning && (
<ConfirmPageContainerWarning warning={ethGasPriceWarning} />
)}
<ConfirmPageContainerSummary
className={classnames({
'confirm-page-container-summary--border':

View File

@ -1,7 +1,6 @@
.confirm-page-container-warning {
background-color: #fffcdb;
display: flex;
justify-content: center;
align-items: center;
border-bottom: 1px solid $geyser;
padding: 12px 24px;

View File

@ -43,6 +43,7 @@ export default class ConfirmPageContainer extends Component {
warning: PropTypes.string,
unapprovedTxCount: PropTypes.number,
origin: PropTypes.string.isRequired,
ethGasPriceWarning: PropTypes.string,
// Navigation
totalTx: PropTypes.number,
positionOfCurrentTx: PropTypes.number,
@ -103,6 +104,7 @@ export default class ConfirmPageContainer extends Component {
hideSenderToRecipient,
showAccountInHeader,
origin,
ethGasPriceWarning,
} = this.props;
const renderAssetImage = contentComponent || !identiconAddress;
@ -162,6 +164,7 @@ export default class ConfirmPageContainer extends Component {
unapprovedTxCount={unapprovedTxCount}
rejectNText={this.context.t('rejectTxsN', [unapprovedTxCount])}
origin={origin}
ethGasPriceWarning={ethGasPriceWarning}
/>
)}
{contentComponent && (

View File

@ -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 (

View File

@ -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' },
]);
});
});

View File

@ -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,
};
}

View File

@ -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';

View File

@ -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 ? (
<Loading />
@ -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 */

View File

@ -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 (
<div className="confirm-page-container-content__details">
@ -253,18 +284,26 @@ export default class ConfirmTransactionBase extends Component {
<ConfirmDetailRow
label="Gas Fee"
value={hexTransactionFee}
headerText={notMainnetOrTest ? '' : 'Edit'}
headerText={notMainnetOrTest || gasPriceFetchFailure ? '' : 'Edit'}
headerTextClassName={
notMainnetOrTest ? '' : 'confirm-detail-row__header-text--edit'
notMainnetOrTest || gasPriceFetchFailure
? ''
: 'confirm-detail-row__header-text--edit'
}
onHeaderClick={
notMainnetOrTest || gasPriceFetchFailure
? null
: () => this.handleEditGas()
}
onHeaderClick={notMainnetOrTest ? null : () => this.handleEditGas()}
secondaryText={
hideFiatConversion
? this.context.t('noConversionRateAvailable')
: ''
}
/>
{advancedInlineGasShown || notMainnetOrTest ? (
{advancedInlineGasShown ||
notMainnetOrTest ||
gasPriceFetchFailure ? (
<AdvancedGasInputs
updateCustomGasPrice={(newGasPrice) =>
updateGasAndCalculate({ ...customGas, gasPrice: newGasPrice })
@ -279,6 +318,11 @@ export default class ConfirmTransactionBase extends Component {
isSpeedUp={false}
/>
) : null}
{noGasPrice ? (
<div className="confirm-page-container-content__error-container">
<ErrorMessage errorKey={GAS_PRICE_FETCH_FAILURE_ERROR_KEY} />
</div>
) : null}
</div>
<div
className={
@ -672,7 +716,12 @@ export default class ConfirmTransactionBase extends Component {
showAccountInHeader,
txData,
} = this.props;
const { submitting, submitError, submitWarning } = this.state;
const {
submitting,
submitError,
submitWarning,
ethGasPriceWarning,
} = this.state;
const { name } = methodData;
const { valid, errorKey } = this.getErrorKey();
@ -696,7 +745,6 @@ export default class ConfirmTransactionBase extends Component {
functionType = t('contractInteraction');
}
}
return (
<ConfirmPageContainer
fromName={fromName}
@ -739,6 +787,7 @@ export default class ConfirmTransactionBase extends Component {
onSubmit={() => this.handleSubmit()}
hideSenderToRecipient={hideSenderToRecipient}
origin={txData.origin}
ethGasPriceWarning={ethGasPriceWarning}
/>
);
}

View File

@ -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,

View File

@ -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 (
<PageContainerContent>
<div className="send-v2__form">
{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 (
<Dialog type="warning" className="send__error-dialog">
{t(warning)}
{gasWarning === '' ? t(warning) : t(gasWarning)}
</Dialog>
);
}
renderError(gasError = false) {
renderError(gasError = '') {
const { t } = this.context;
const { error } = this.props;
return (
<Dialog type="error" className="send__error-dialog">
{gasError ? t('gasPriceExcessive') : t(error)}
{gasError === '' ? t(error) : t(gasError)}
</Dialog>
);
}

View File

@ -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),
};
}

View File

@ -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 (
<div
className="advanced-gas-options-btn"
@ -92,8 +102,11 @@ export default class SendGasRow extends Component {
gasLimit,
insufficientBalance,
isMainnet,
isEthGasPrice,
noGasPrice,
} = this.props;
const { metricsEvent } = this.context;
const gasPriceFetchFailure = isEthGasPrice || noGasPrice;
const gasPriceButtonGroup = (
<div>
@ -148,7 +161,11 @@ export default class SendGasRow extends Component {
</div>
);
// 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;

View File

@ -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,
};
}

View File

@ -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;
}

View File

@ -49,6 +49,7 @@ describe('SendFooter Component', () => {
update={propsMethodSpies.update}
sendErrors={{}}
mostRecentOverviewPage="mostRecentOverviewPage"
noGasPrice={false}
/>,
{ context: { t: (str) => str, metricsEvent: () => ({}) } },
);

View File

@ -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),
};
}

View File

@ -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');
}