1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 01:39:44 +01:00

Fixes updates on the confirm screen. (#11788)

* Fixes updates on the confirm screen.

* Better handling of internal send transactions

* maxFee -> maxFeePerGas property name fix

* Remove redundant setEstimateToUse call in onManualChange

* Fix unit tests

* rebase error fix

* Fixes to speedup loading and transaction breakdown priority fee

* Fix lint and unit tests

* Ensure gas price based transaction that have been customized (e.g. speed up and retry) are properly initialized in useGasFeeInputs

* Clean up

* Link fix
This commit is contained in:
Dan J Miller 2021-08-06 17:01:30 -02:30 committed by GitHub
parent 827e6a6efc
commit bfde5a1d77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 156 additions and 111 deletions

View File

@ -73,6 +73,7 @@ const initialState = {
customGasPrice: null,
customMaxFeePerGas: null,
customMaxPriorityFeePerGas: null,
swapsUserFeeLevel: '',
selectedAggId: null,
customApproveTxData: '',
errorKey: '',
@ -456,6 +457,13 @@ export default class SwapsController {
});
}
setSwapsUserFeeLevel(swapsUserFeeLevel) {
const { swapsState } = this.store.getState();
this.store.updateState({
swapsState: { ...swapsState, swapsUserFeeLevel },
});
}
setSwapsTxMaxFeePriorityPerGas(maxPriorityFeePerGas) {
const { swapsState } = this.store.getState();
this.store.updateState({

View File

@ -133,6 +133,7 @@ const EMPTY_INIT_STATE = {
swapsFeatureIsLive: true,
useNewSwapsApi: false,
swapsQuoteRefreshTime: 60000,
swapsUserFeeLevel: '',
},
};

View File

@ -433,7 +433,20 @@ export default class TransactionController extends EventEmitter {
) {
txMeta.txParams.maxFeePerGas = txMeta.txParams.gasPrice;
txMeta.txParams.maxPriorityFeePerGas = txMeta.txParams.gasPrice;
txMeta.userFeeLevel = 'custom';
} else {
if (
(defaultMaxFeePerGas &&
defaultMaxPriorityFeePerGas &&
!txMeta.txParams.maxFeePerGas &&
!txMeta.txParams.maxPriorityFeePerGas) ||
txMeta.origin === 'metamask'
) {
txMeta.userFeeLevel = 'medium';
} else {
txMeta.userFeeLevel = 'custom';
}
if (defaultMaxFeePerGas && !txMeta.txParams.maxFeePerGas) {
// If the dapp has not set the gasPrice or the maxFeePerGas, then we set maxFeePerGas
// with the one returned by the gasFeeController, if that is available.

View File

@ -1089,6 +1089,10 @@ export default class MetamaskController extends EventEmitter {
swapsController.setSwapsLiveness,
swapsController,
),
setSwapsUserFeeLevel: nodeify(
swapsController.setSwapsUserFeeLevel,
swapsController,
),
// MetaMetrics
trackMetaMetricsEvent: nodeify(

View File

@ -40,7 +40,8 @@ export default function AdvancedGasControls({
const showFeeMarketFields =
networkAndAccountSupport1559 &&
(gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET ||
gasEstimateType === GAS_ESTIMATE_TYPES.ETH_GASPRICE);
gasEstimateType === GAS_ESTIMATE_TYPES.ETH_GASPRICE ||
isGasEstimatesLoading);
return (
<div className="advanced-gas-controls">

View File

@ -77,7 +77,9 @@ export default function EditGasDisplay({
);
const [showAdvancedForm, setShowAdvancedForm] = useState(
!estimateToUse || !networkAndAccountSupport1559,
!estimateToUse ||
estimateToUse === 'custom' ||
!networkAndAccountSupport1559,
);
const [hideRadioButtons, setHideRadioButtons] = useState(
showAdvancedInlineGasIfPossible,
@ -110,7 +112,7 @@ export default function EditGasDisplay({
return (
<div className="edit-gas-display">
<div className="edit-gas-display__content">
{warning && (
{warning && !isGasEstimatesLoading && (
<div className="edit-gas-display__warning">
<ActionableMessage
className="actionable-message--warning"
@ -118,12 +120,12 @@ export default function EditGasDisplay({
/>
</div>
)}
{showTopError && (
{showTopError && !isGasEstimatesLoading && (
<div className="edit-gas-display__warning">
<ErrorMessage errorKey={errorKey} />
</div>
)}
{requireDappAcknowledgement && (
{requireDappAcknowledgement && !isGasEstimatesLoading && (
<div className="edit-gas-display__dapp-acknowledgement-warning">
<ActionableMessage
className="actionable-message--warning"

View File

@ -25,6 +25,7 @@ import {
hideSidebar,
updateTransaction,
updateCustomSwapsEIP1559GasParams,
updateSwapsUserFeeLevel,
} from '../../../store/actions';
import LoadingHeartBeat from '../../ui/loading-heartbeat';
import { checkNetworkAndAccountSupports1559 } from '../../../selectors';
@ -126,6 +127,15 @@ export default function EditGasPopover({
gasPrice: decGWEIToHexWEI(gasPrice),
};
const updatedTxMeta = {
...transaction,
userFeeLevel: estimateToUse || 'custom',
txParams: {
...transaction.txParams,
...newGasSettings,
},
};
switch (mode) {
case EDIT_GAS_MODES.CANCEL:
dispatch(createCancelTransaction(transaction.id, newGasSettings));
@ -134,19 +144,12 @@ export default function EditGasPopover({
dispatch(createSpeedUpTransaction(transaction.id, newGasSettings));
break;
case EDIT_GAS_MODES.MODIFY_IN_PLACE:
dispatch(
updateTransaction({
...transaction,
txParams: {
...transaction.txParams,
...newGasSettings,
},
}),
);
dispatch(updateTransaction(updatedTxMeta));
break;
case EDIT_GAS_MODES.SWAPS:
// This popover component should only be used for the "FEE_MARKET" type in Swaps.
if (networkAndAccountSupport1559) {
dispatch(updateSwapsUserFeeLevel(estimateToUse || 'custom'));
dispatch(updateCustomSwapsEIP1559GasParams(newGasSettings));
}
break;
@ -165,6 +168,7 @@ export default function EditGasPopover({
maxFeePerGas,
maxPriorityFeePerGas,
networkAndAccountSupport1559,
estimateToUse,
]);
let title = t('editGasTitle');

View File

@ -220,6 +220,7 @@ export default function TransactionListItem({
mode={EDIT_GAS_MODES.SPEED_UP}
transaction={{
...transactionGroup.primaryTransaction,
userFeeLevel: 'custom',
txParams: {
...transactionGroup.primaryTransaction?.txParams,
...customRetryGasSettings,
@ -233,6 +234,7 @@ export default function TransactionListItem({
mode={EDIT_GAS_MODES.CANCEL}
transaction={{
...transactionGroup.primaryTransaction,
userFeeLevel: 'custom',
txParams: {
...transactionGroup.primaryTransaction?.txParams,
...customCancelGasSettings,

View File

@ -251,6 +251,9 @@ export const getCustomMaxFeePerGas = (state) =>
export const getCustomMaxPriorityFeePerGas = (state) =>
state.metamask.swapsState.customMaxPriorityFeePerGas;
export const getSwapsUserFeeLevel = (state) =>
state.metamask.swapsState.swapsUserFeeLevel;
export const getFetchParams = (state) => state.metamask.swapsState.fetchParams;
export const getQuotes = (state) => state.metamask.swapsState.quotes;

View File

@ -172,7 +172,7 @@ export function addHexes(aHexWEI, bHexWEI) {
}
export function subtractHexes(aHexWEI, bHexWEI) {
return addCurrencies(aHexWEI, bHexWEI, {
return subtractCurrencies(aHexWEI, bHexWEI, {
aBase: 16,
bBase: 16,
toNumericBase: 'hex',

View File

@ -1,7 +1,7 @@
import { addHexPrefix } from 'ethereumjs-util';
import { useCallback, useState } from 'react';
import { useSelector } from 'react-redux';
import { findKey, isEqual } from 'lodash';
import { isEqual } from 'lodash';
import {
GAS_ESTIMATE_TYPES,
EDIT_GAS_MODES,
@ -103,32 +103,6 @@ function getGasFeeEstimate(
return String(fallback);
}
/**
* This method tries to determine if any estimate level matches the
* current maxFeePerGas and maxPriorityFeePerGas values. If we find
* a match, we can pre-select a radio button in the RadioGroup
*/
function getMatchingEstimateFromGasFees(
gasFeeEstimates,
maxFeePerGas,
maxPriorityFeePerGas,
gasPrice,
networkAndAccountSupports1559,
) {
return (
findKey(gasFeeEstimates, (estimate) => {
if (networkAndAccountSupports1559) {
return (
Number(estimate?.suggestedMaxPriorityFeePerGas) ===
Number(maxPriorityFeePerGas) &&
Number(estimate?.suggestedMaxFeePerGas) === Number(maxFeePerGas)
);
}
return estimate?.gasPrice === gasPrice;
}) || null
);
}
/**
* @typedef {Object} GasFeeInputReturnType
* @property {DecGweiString} [maxFeePerGas] - the maxFeePerGas input value.
@ -229,33 +203,30 @@ export function useGasFeeInputs(
);
const [initialMatchingEstimateLevel] = useState(
getMatchingEstimateFromGasFees(
gasFeeEstimates,
initialMaxFeePerGas,
initialMaxPriorityFeePerGas,
initialGasPrice,
networkAndAccountSupports1559,
),
transaction?.userFeeLevel || null,
);
const initialFeeParamsAreCustom =
initialMatchingEstimateLevel === 'custom' ||
initialMatchingEstimateLevel === null;
// This hook keeps track of a few pieces of transitional state. It is
// transitional because it is only used to modify a transaction in the
// metamask (background) state tree.
const [maxFeePerGas, setMaxFeePerGas] = useState(
initialMaxFeePerGas && !initialMatchingEstimateLevel
initialMaxFeePerGas && initialFeeParamsAreCustom
? initialMaxFeePerGas
: null,
);
const [maxPriorityFeePerGas, setMaxPriorityFeePerGas] = useState(
initialMaxPriorityFeePerGas && !initialMatchingEstimateLevel
initialMaxPriorityFeePerGas && initialFeeParamsAreCustom
? initialMaxPriorityFeePerGas
: null,
);
const [gasPriceHasBeenManuallySet, setGasPriceHasBeenManuallySet] = useState(
false,
initialMatchingEstimateLevel === 'custom',
);
const [gasPrice, setGasPrice] = useState(
initialGasPrice && !initialMatchingEstimateLevel ? initialGasPrice : null,
initialGasPrice && initialFeeParamsAreCustom ? initialGasPrice : null,
);
const [gasLimit, setGasLimit] = useState(
Number(hexToDecimal(transaction?.txParams?.gas ?? minimumGasLimit)),
@ -265,7 +236,7 @@ export function useGasFeeInputs(
const dontDefaultToAnEstimateLevel =
userPrefersAdvancedGas &&
transaction?.txParams?.maxPriorityFeePerGas &&
transaction?.txParams?.gasPrice;
transaction?.txParams?.maxFeePerGas;
const initialEstimateToUse = transaction
? initialMatchingEstimateLevel
@ -514,28 +485,28 @@ export function useGasFeeInputs(
{ value: ethBalance, fromNumericBase: 'hex' },
);
const handleGasLimitOutOfBoundError = useCallback(() => {
if (gasErrors.gasLimit === GAS_FORM_ERRORS.GAS_LIMIT_OUT_OF_BOUNDS) {
const transactionGasLimitDec = hexToDecimal(transaction?.txParams?.gas);
const minimumGasLimitDec = hexToDecimal(minimumGasLimit);
setGasLimit(
transactionGasLimitDec > minimumGasLimitDec
? transactionGasLimitDec
: minimumGasLimitDec,
);
}
}, [minimumGasLimit, gasErrors.gasLimit, transaction]);
// When a user selects an estimate level, it will wipe out what they have
// previously put in the inputs. This returns the inputs to the estimated
// values at the level specified.
const setEstimateToUse = useCallback(
(estimateLevel) => {
setInternalEstimateToUse(estimateLevel);
if (gasErrors.gasLimit === GAS_FORM_ERRORS.GAS_LIMIT_OUT_OF_BOUNDS) {
const transactionGasLimit = hexToDecimal(transaction?.txParams?.gas);
const minimumGasLimitDec = hexToDecimal(minimumGasLimit);
setGasLimit(
transactionGasLimit > minimumGasLimitDec
? transactionGasLimit
: minimumGasLimitDec,
);
}
setMaxFeePerGas(null);
setMaxPriorityFeePerGas(null);
setGasPrice(null);
setGasPriceHasBeenManuallySet(false);
},
[minimumGasLimit, gasErrors.gasLimit, transaction],
);
const setEstimateToUse = (estimateLevel) => {
setInternalEstimateToUse(estimateLevel);
handleGasLimitOutOfBoundError();
setMaxFeePerGas(null);
setMaxPriorityFeePerGas(null);
setGasPrice(null);
setGasPriceHasBeenManuallySet(false);
};
return {
maxFeePerGas: maxFeePerGasToUse,
@ -561,7 +532,8 @@ export function useGasFeeInputs(
gasErrors: errorsAndWarnings,
hasGasErrors: hasBlockingGasErrors,
onManualChange: () => {
setEstimateToUse(null);
setInternalEstimateToUse('custom');
handleGasLimitOutOfBoundError();
// Restore existing values
setGasPrice(gasPriceToUse);
setGasLimit(gasLimit);

View File

@ -13,7 +13,6 @@ import {
getTokenValueParam,
} from '../../helpers/utils/token-util';
import { hexWEIToDecETH } from '../../helpers/utils/conversions.util';
import { getHexGasTotal } from '../../helpers/utils/confirm-tx.util';
import ConfirmTokenTransactionBase from './confirm-token-transaction-base.component';
const mapStateToProps = (state, ownProps) => {
@ -34,32 +33,28 @@ const mapStateToProps = (state, ownProps) => {
const {
txData: {
id: transactionId,
txParams: { to: tokenAddress, data, maxFeePerGas, gasPrice, gas } = {},
txParams: { to: tokenAddress, data } = {},
} = {},
} = confirmTransaction;
const ethTransactionTotalMaxAmount = Number(
hexWEIToDecETH(
getHexGasTotal({
gasPrice: maxFeePerGas ?? gasPrice,
gasLimit: gas,
}),
),
).toFixed(6);
const transaction =
currentNetworkTxList.find(
({ id }) => id === (Number(paramsTransactionId) || transactionId),
) || {};
const { ethTransactionTotal, fiatTransactionTotal } = transactionFeeSelector(
state,
transaction,
);
const {
ethTransactionTotal,
fiatTransactionTotal,
hexMaximumTransactionFee,
} = transactionFeeSelector(state, transaction);
const tokens = getTokens(state);
const currentToken = tokens?.find(({ address }) => tokenAddress === address);
const { decimals, symbol: tokenSymbol } = currentToken || {};
const ethTransactionTotalMaxAmount = Number(
hexWEIToDecETH(hexMaximumTransactionFee),
).toFixed(6);
const tokenData = getTokenData(data);
const tokenValue = getTokenValueParam(tokenData);
const toAddress = getTokenAddressParam(tokenData);

View File

@ -4,7 +4,6 @@ import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../shared/constants/app';
import { getEnvironmentType } from '../../../app/scripts/lib/util';
import ConfirmPageContainer from '../../components/app/confirm-page-container';
import { isBalanceSufficient } from '../send/send.utils';
import { getHexGasTotal } from '../../helpers/utils/confirm-tx.util';
import {
addHexes,
hexToDecimal,
@ -110,6 +109,8 @@ export default class ConfirmTransactionBase extends Component {
gasIsLoading: PropTypes.bool,
primaryTotalTextOverrideMaxAmount: PropTypes.string,
useNativeCurrencyAsPrimaryCurrency: PropTypes.bool,
maxFeePerGas: PropTypes.string,
maxPriorityFeePerGas: PropTypes.string,
};
state = {
@ -275,6 +276,7 @@ export default class ConfirmTransactionBase extends Component {
primaryTotalTextOverride,
secondaryTotalTextOverride,
hexMinimumTransactionFee,
hexMaximumTransactionFee,
hexTransactionTotal,
useNonceField,
customNonceValue,
@ -283,8 +285,9 @@ export default class ConfirmTransactionBase extends Component {
getNextNonce,
txData,
useNativeCurrencyAsPrimaryCurrency,
hexMaximumTransactionFee,
primaryTotalTextOverrideMaxAmount,
maxFeePerGas,
maxPriorityFeePerGas,
} = this.props;
const { t } = this.context;
@ -305,14 +308,7 @@ export default class ConfirmTransactionBase extends Component {
return (
<UserPreferencedCurrencyDisplay
type={PRIMARY}
value={addHexes(
txData.txParams.value,
getHexGasTotal({
gasPrice:
txData.txParams.maxFeePerGas ?? txData.txParams.gasPrice,
gasLimit: txData.txParams.gas,
}),
)}
value={addHexes(txData.txParams.value, hexMaximumTransactionFee)}
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
/>
);
@ -467,9 +463,12 @@ export default class ConfirmTransactionBase extends Component {
subTitle={
<GasTiming
maxPriorityFeePerGas={hexWEIToDecGWEI(
txData.txParams.maxPriorityFeePerGas,
maxPriorityFeePerGas ||
txData.txParams.maxPriorityFeePerGas,
)}
maxFeePerGas={hexWEIToDecGWEI(
maxFeePerGas || txData.txParams.maxFeePerGas,
)}
maxFeePerGas={hexWEIToDecGWEI(txData.txParams.maxFeePerGas)}
/>
}
/>,
@ -611,6 +610,8 @@ export default class ConfirmTransactionBase extends Component {
history,
mostRecentOverviewPage,
updateCustomNonce,
maxFeePerGas,
maxPriorityFeePerGas,
} = this.props;
const { submitting } = this.state;
@ -618,6 +619,20 @@ export default class ConfirmTransactionBase extends Component {
return;
}
if (maxFeePerGas) {
txData.txParams = {
...txData.txParams,
maxFeePerGas,
};
}
if (maxPriorityFeePerGas) {
txData.txParams = {
...txData.txParams,
maxPriorityFeePerGas,
};
}
this.setState(
{
submitting: true,

View File

@ -118,6 +118,7 @@ const mapStateToProps = (state, ownProps) => {
hexMinimumTransactionFee,
hexMaximumTransactionFee,
hexTransactionTotal,
gasEstimationObject,
} = transactionFeeSelector(state, transaction);
if (transaction && transaction.simulationFails) {
@ -152,8 +153,9 @@ const mapStateToProps = (state, ownProps) => {
}
customNonceValue = getCustomNonceValue(state);
const isEthGasPrice = getIsEthGasPriceFetched(state);
const noGasPrice = getNoGasPriceFetched(state);
const noGasPrice = !supportsEIP1599 && getNoGasPriceFetched(state);
const { useNativeCurrencyAsPrimaryCurrency } = getPreferences(state);
return {
balance,
fromAddress,
@ -196,6 +198,8 @@ const mapStateToProps = (state, ownProps) => {
supportsEIP1599,
gasIsLoading: isGasEstimatesLoading || gasLoadingAnimationIsShowing,
useNativeCurrencyAsPrimaryCurrency,
maxFeePerGas: gasEstimationObject.maxFeePerGas,
maxPriorityFeePerGas: gasEstimationObject.maxPriorityFeePerGas,
};
};

View File

@ -26,6 +26,7 @@ import {
getCustomSwapsGas, // Gas limit.
getCustomMaxFeePerGas,
getCustomMaxPriorityFeePerGas,
getSwapsUserFeeLevel,
getDestinationTokenInfo,
getUsedSwapsGasPrice,
getTopQuote,
@ -128,6 +129,7 @@ export default function ViewQuote() {
const customMaxGas = useSelector(getCustomSwapsGas);
const customMaxFeePerGas = useSelector(getCustomMaxFeePerGas);
const customMaxPriorityFeePerGas = useSelector(getCustomMaxPriorityFeePerGas);
const swapsUserFeeLevel = useSelector(getSwapsUserFeeLevel);
const tokenConversionRates = useSelector(getTokenExchangeRates);
const memoizedTokenConversionRates = useEqualityCheck(tokenConversionRates);
const { balance: ethBalance } = useSelector(getSelectedAccount);
@ -153,7 +155,9 @@ export default function ViewQuote() {
if (networkAndAccountSupports1559) {
// For Swaps we want to get 'high' estimations by default.
// eslint-disable-next-line react-hooks/rules-of-hooks
gasFeeInputs = useGasFeeInputs('high');
gasFeeInputs = useGasFeeInputs('high', {
userFeeLevel: swapsUserFeeLevel || 'high',
});
}
const { isBestQuote } = usedQuote;
@ -670,6 +674,7 @@ export default function ViewQuote() {
{showEditGasPopover && networkAndAccountSupports1559 && (
<EditGasPopover
transaction={{
userFeeLevel: swapsUserFeeLevel || 'high',
txParams: {
maxFeePerGas,
maxPriorityFeePerGas,

View File

@ -240,20 +240,28 @@ export const transactionFeeSelector = function (state, txData) {
};
if (networkAndAccountSupportsEIP1559) {
const { medium = {}, gasPrice = '0' } = gasFeeEstimates;
const { gasPrice = '0' } = gasFeeEstimates;
const selectedGasEstimates = gasFeeEstimates[txData.userFeeLevel] || {};
if (txData.txParams?.type === TRANSACTION_ENVELOPE_TYPES.LEGACY) {
gasEstimationObject.gasPrice =
txData.txParams?.gasPrice ?? decGWEIToHexWEI(gasPrice);
} else {
const { suggestedMaxPriorityFeePerGas, suggestedMaxFeePerGas } = medium;
const {
suggestedMaxPriorityFeePerGas,
suggestedMaxFeePerGas,
} = selectedGasEstimates;
gasEstimationObject.maxFeePerGas =
txData.txParams?.maxFeePerGas ??
decGWEIToHexWEI(suggestedMaxFeePerGas || gasPrice);
txData.txParams?.maxFeePerGas &&
(txData.userFeeLevel === 'custom' || !suggestedMaxFeePerGas)
? txData.txParams?.maxFeePerGas
: decGWEIToHexWEI(suggestedMaxFeePerGas || gasPrice);
gasEstimationObject.maxPriorityFeePerGas =
txData.txParams?.maxPriorityFeePerGas ??
((suggestedMaxPriorityFeePerGas &&
decGWEIToHexWEI(suggestedMaxPriorityFeePerGas)) ||
gasEstimationObject.maxFeePerGas);
txData.txParams?.maxPriorityFeePerGas &&
(txData.userFeeLevel === 'custom' || !suggestedMaxPriorityFeePerGas)
? txData.txParams?.maxPriorityFeePerGas
: (suggestedMaxPriorityFeePerGas &&
decGWEIToHexWEI(suggestedMaxPriorityFeePerGas)) ||
gasEstimationObject.maxFeePerGas;
gasEstimationObject.baseFeePerGas = decGWEIToHexWEI(
gasFeeEstimates.estimatedBaseFee,
);
@ -346,5 +354,6 @@ export const transactionFeeSelector = function (state, txData) {
fiatTransactionTotal,
ethTransactionTotal,
hexTransactionTotal,
gasEstimationObject,
};
};

View File

@ -2215,6 +2215,13 @@ export function updateCustomSwapsEIP1559GasParams({
};
}
export function updateSwapsUserFeeLevel(swapsCustomUserFeeLevel) {
return async (dispatch) => {
await promisifiedBackground.setSwapsUserFeeLevel(swapsCustomUserFeeLevel);
await forceUpdateMetamaskState(dispatch);
};
}
export function customSwapsGasParamsUpdated(gasLimit, gasPrice) {
return async (dispatch) => {
await promisifiedBackground.setSwapsTxGasPrice(gasPrice);