mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-22 17:33:23 +01:00
Unable to proceed with tx bc of inaccurate/overly aggressive "Insufficient funds for gas" warning #13087 (#14634)
Co-authored-by: VSaric <vladimir.saric@consensys.net>
This commit is contained in:
parent
f44af06f9b
commit
2680340e27
7
app/_locales/en/messages.json
generated
7
app/_locales/en/messages.json
generated
@ -978,6 +978,9 @@
|
||||
"deleteNetworkDescription": {
|
||||
"message": "Are you sure you want to delete this network?"
|
||||
},
|
||||
"deposit": {
|
||||
"message": "Deposit"
|
||||
},
|
||||
"depositCrypto": {
|
||||
"message": "Deposit $1",
|
||||
"description": "$1 represents the crypto symbol to be purchased"
|
||||
@ -1756,6 +1759,10 @@
|
||||
"message": "You do not have enough $1 in your account to pay for transaction fees on $2 network. $3 or deposit from another account.",
|
||||
"description": "$1 is the native currency of the network, $2 is the name of the current network, $3 is the key 'buy' + the ticker symbol of the native currency of the chain wrapped in a button"
|
||||
},
|
||||
"insufficientCurrencyBuyOrReceive": {
|
||||
"message": "You do not have enough $1 in your account to pay for transaction fees on $2 network. $3 or $4 from another account.",
|
||||
"description": "$1 is the native currency of the network, $2 is the name of the current network, $3 is the key 'buy' + the ticker symbol of the native currency of the chain wrapped in a button, $4 is the key 'deposit' button"
|
||||
},
|
||||
"insufficientCurrencyDeposit": {
|
||||
"message": "You do not have enough $1 in your account to pay for transaction fees on $2 network. Deposit $1 from another account.",
|
||||
"description": "$1 is the native currency of the network, $2 is the name of the current network"
|
||||
|
@ -37,8 +37,8 @@ describe('Send ETH from inside MetaMask using default gas', function () {
|
||||
const errorAmount = await driver.findElement('.send-v2__error-amount');
|
||||
assert.equal(
|
||||
await errorAmount.getText(),
|
||||
'Insufficient funds.',
|
||||
'send screen should render an insufficient fund error message',
|
||||
'Insufficient funds for gas',
|
||||
'send screen should render an insufficient fund for gas error message',
|
||||
);
|
||||
|
||||
await inputAmount.press(driver.Key.BACK_SPACE);
|
||||
|
@ -14,6 +14,7 @@ import {
|
||||
getGasFeeEstimates,
|
||||
getIsGasEstimatesLoading,
|
||||
} from '../../../ducks/metamask/metamask';
|
||||
import { getEIP1559V2Enabled } from '../../../selectors';
|
||||
|
||||
import Typography from '../../ui/typography/typography';
|
||||
import {
|
||||
@ -45,6 +46,7 @@ export default function GasTiming({
|
||||
const gasEstimateType = useSelector(getGasEstimateType);
|
||||
const gasFeeEstimates = useSelector(getGasFeeEstimates);
|
||||
const isGasEstimatesLoading = useSelector(getIsGasEstimatesLoading);
|
||||
const eip1559V2Enabled = useSelector(getEIP1559V2Enabled);
|
||||
|
||||
const [customEstimatedTime, setCustomEstimatedTime] = useState(null);
|
||||
const t = useContext(I18nContext);
|
||||
@ -195,8 +197,10 @@ export default function GasTiming({
|
||||
<Typography
|
||||
variant={TYPOGRAPHY.H7}
|
||||
className={classNames('gas-timing', {
|
||||
[`gas-timing--${attitude}`]: attitude && !supportsEIP1559V2,
|
||||
[`gas-timing--${attitude}-V2`]: attitude && supportsEIP1559V2,
|
||||
[`gas-timing--${attitude}`]:
|
||||
attitude && (eip1559V2Enabled || !supportsEIP1559V2),
|
||||
[`gas-timing--${attitude}-V2`]:
|
||||
attitude && (eip1559V2Enabled || supportsEIP1559V2),
|
||||
})}
|
||||
>
|
||||
{text}
|
||||
|
@ -13,6 +13,7 @@ import { GAS_ESTIMATE_TYPES, GAS_LIMITS } from '../../../shared/constants/gas';
|
||||
import {
|
||||
CONTRACT_ADDRESS_ERROR,
|
||||
INSUFFICIENT_FUNDS_ERROR,
|
||||
INSUFFICIENT_FUNDS_FOR_GAS_ERROR,
|
||||
INSUFFICIENT_TOKENS_ERROR,
|
||||
INVALID_RECIPIENT_ADDRESS_ERROR,
|
||||
INVALID_RECIPIENT_ADDRESS_NOT_ETH_NETWORK_ERROR,
|
||||
@ -1277,7 +1278,7 @@ const slice = createSlice({
|
||||
const draftTransaction =
|
||||
state.draftTransactions[state.currentTransactionUUID];
|
||||
switch (true) {
|
||||
// set error to INSUFFICIENT_FUNDS_ERROR if the account balance is lower
|
||||
// set error to INSUFFICIENT_FUNDS_FOR_GAS_ERROR if the account balance is lower
|
||||
// than the total price of the transaction inclusive of gas fees.
|
||||
case draftTransaction.asset.type === ASSET_TYPES.NATIVE &&
|
||||
!isBalanceSufficient({
|
||||
@ -1285,9 +1286,9 @@ const slice = createSlice({
|
||||
balance: draftTransaction.asset.balance,
|
||||
gasTotal: draftTransaction.gas.gasTotal ?? '0x0',
|
||||
}):
|
||||
draftTransaction.amount.error = INSUFFICIENT_FUNDS_ERROR;
|
||||
draftTransaction.amount.error = INSUFFICIENT_FUNDS_FOR_GAS_ERROR;
|
||||
break;
|
||||
// set error to INSUFFICIENT_FUNDS_ERROR if the token balance is lower
|
||||
// set error to INSUFFICIENT_TOKENS_ERROR if the token balance is lower
|
||||
// than the amount of token the user is attempting to send.
|
||||
case draftTransaction.asset.type === ASSET_TYPES.TOKEN &&
|
||||
!isTokenBalanceSufficient({
|
||||
|
@ -5,6 +5,7 @@ import { ethers } from 'ethers';
|
||||
import {
|
||||
CONTRACT_ADDRESS_ERROR,
|
||||
INSUFFICIENT_FUNDS_ERROR,
|
||||
INSUFFICIENT_FUNDS_FOR_GAS_ERROR,
|
||||
INSUFFICIENT_TOKENS_ERROR,
|
||||
INVALID_RECIPIENT_ADDRESS_ERROR,
|
||||
KNOWN_RECIPIENT_ADDRESS_WARNING,
|
||||
@ -856,7 +857,7 @@ describe('Send Slice', () => {
|
||||
const draftTransaction = getTestUUIDTx(result);
|
||||
|
||||
expect(draftTransaction.amount.error).toStrictEqual(
|
||||
INSUFFICIENT_FUNDS_ERROR,
|
||||
INSUFFICIENT_FUNDS_FOR_GAS_ERROR,
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
@import 'create-account/index';
|
||||
@import 'error/index';
|
||||
@import 'first-time-flow/index';
|
||||
@import 'send/gas-display/index';
|
||||
@import 'home/index';
|
||||
@import 'keychains/index';
|
||||
@import 'permissions-connect/index';
|
||||
|
500
ui/pages/send/gas-display/gas-display.js
Normal file
500
ui/pages/send/gas-display/gas-display.js
Normal file
@ -0,0 +1,500 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { I18nContext } from '../../../contexts/i18n';
|
||||
import { useGasFeeContext } from '../../../contexts/gasFee';
|
||||
import { PRIMARY, SECONDARY } from '../../../helpers/constants/common';
|
||||
import { isLegacyTransaction } from '../../../helpers/utils/transactions.util';
|
||||
import { hexWEIToDecGWEI } from '../../../../shared/lib/transactions-controller-utils';
|
||||
import UserPreferencedCurrencyDisplay from '../../../components/app/user-preferenced-currency-display';
|
||||
import GasTiming from '../../../components/app/gas-timing';
|
||||
import InfoTooltip from '../../../components/ui/info-tooltip';
|
||||
import Typography from '../../../components/ui/typography';
|
||||
import Button from '../../../components/ui/button';
|
||||
import Box from '../../../components/ui/box';
|
||||
import {
|
||||
TYPOGRAPHY,
|
||||
DISPLAY,
|
||||
FLEX_DIRECTION,
|
||||
BLOCK_SIZES,
|
||||
COLORS,
|
||||
FONT_STYLE,
|
||||
FONT_WEIGHT,
|
||||
} from '../../../helpers/constants/design-system';
|
||||
import {
|
||||
ERC1155,
|
||||
ERC20,
|
||||
ERC721,
|
||||
} from '../../../../shared/constants/transaction';
|
||||
import LoadingHeartBeat from '../../../components/ui/loading-heartbeat';
|
||||
import TransactionDetailItem from '../../../components/app/transaction-detail-item';
|
||||
import { NETWORK_TO_NAME_MAP } from '../../../../shared/constants/network';
|
||||
import TransactionDetail from '../../../components/app/transaction-detail';
|
||||
import ActionableMessage from '../../../components/ui/actionable-message';
|
||||
import DepositPopover from '../../../components/app/deposit-popover';
|
||||
import {
|
||||
getProvider,
|
||||
getPreferences,
|
||||
getIsBuyableChain,
|
||||
transactionFeeSelector,
|
||||
getIsMainnet,
|
||||
getEIP1559V2Enabled,
|
||||
checkNetworkAndAccountSupports1559,
|
||||
} from '../../../selectors';
|
||||
|
||||
import {
|
||||
hexWEIToDecETH,
|
||||
addHexes,
|
||||
} from '../../../helpers/utils/conversions.util';
|
||||
import { INSUFFICIENT_TOKENS_ERROR } from '../send.constants';
|
||||
import { getCurrentDraftTransaction } from '../../../ducks/send';
|
||||
import { showModal } from '../../../store/actions';
|
||||
|
||||
export default function GasDisplay({ gasError }) {
|
||||
const t = useContext(I18nContext);
|
||||
const dispatch = useDispatch();
|
||||
const { estimateUsed } = useGasFeeContext();
|
||||
const [showDepositPopover, setShowDepositPopover] = useState(false);
|
||||
const currentProvider = useSelector(getProvider);
|
||||
const isMainnet = useSelector(getIsMainnet);
|
||||
const isBuyableChain = useSelector(getIsBuyableChain);
|
||||
const draftTransaction = useSelector(getCurrentDraftTransaction);
|
||||
const eip1559V2Enabled = useSelector(getEIP1559V2Enabled);
|
||||
const networkAndAccountSupports1559 = useSelector(
|
||||
checkNetworkAndAccountSupports1559,
|
||||
);
|
||||
const { useNativeCurrencyAsPrimaryCurrency } = useSelector(getPreferences);
|
||||
const { nativeCurrency, provider, unapprovedTxs } = useSelector(
|
||||
(state) => state.metamask,
|
||||
);
|
||||
const { confirmTransaction } = useSelector((state) => state);
|
||||
const { txData } = confirmTransaction;
|
||||
const { txParams = {} } = txData;
|
||||
const supportsEIP1559 =
|
||||
networkAndAccountSupports1559 && !isLegacyTransaction(txParams);
|
||||
const { chainId } = provider;
|
||||
const networkName = NETWORK_TO_NAME_MAP[chainId];
|
||||
const isInsufficientTokenError =
|
||||
draftTransaction?.amount.error === INSUFFICIENT_TOKENS_ERROR;
|
||||
const editingTransaction = unapprovedTxs[draftTransaction.id];
|
||||
const supportsEIP1559V2 = eip1559V2Enabled && supportsEIP1559;
|
||||
|
||||
const transactionData = {
|
||||
txParams: {
|
||||
gasPrice: draftTransaction.gas?.gasPrice,
|
||||
gas: editingTransaction?.userEditedGasLimit
|
||||
? editingTransaction?.txParams?.gas
|
||||
: draftTransaction.gas?.gasLimit,
|
||||
maxFeePerGas: editingTransaction?.txParams?.maxFeePerGas
|
||||
? editingTransaction?.txParams?.maxFeePerGas
|
||||
: draftTransaction.gas?.maxFeePerGas,
|
||||
maxPriorityFeePerGas: editingTransaction?.txParams?.maxPriorityFeePerGas
|
||||
? editingTransaction?.txParams?.maxPriorityFeePerGas
|
||||
: draftTransaction.gas?.maxPriorityFeePerGas,
|
||||
value: draftTransaction.amount?.value,
|
||||
type: draftTransaction.transactionType,
|
||||
},
|
||||
userFeeLevel: editingTransaction?.userFeeLevel,
|
||||
};
|
||||
|
||||
const {
|
||||
hexMinimumTransactionFee,
|
||||
hexMaximumTransactionFee,
|
||||
hexTransactionTotal,
|
||||
} = useSelector((state) => transactionFeeSelector(state, transactionData));
|
||||
|
||||
let title;
|
||||
if (
|
||||
draftTransaction?.asset.details?.standard === ERC721 ||
|
||||
draftTransaction?.asset.details?.standard === ERC1155
|
||||
) {
|
||||
title = draftTransaction?.asset.details?.name;
|
||||
} else if (draftTransaction?.asset.details?.standard === ERC20) {
|
||||
title = `${hexWEIToDecETH(draftTransaction.amount.value)} ${
|
||||
draftTransaction?.asset.details?.symbol
|
||||
}`;
|
||||
}
|
||||
|
||||
const ethTransactionTotalMaxAmount = Number(
|
||||
hexWEIToDecETH(hexMaximumTransactionFee),
|
||||
);
|
||||
|
||||
const primaryTotalTextOverrideMaxAmount = `${title} + ${ethTransactionTotalMaxAmount} ${nativeCurrency}`;
|
||||
|
||||
let detailTotal, maxAmount;
|
||||
|
||||
if (draftTransaction?.asset.type === 'NATIVE') {
|
||||
detailTotal = (
|
||||
<Box
|
||||
height={BLOCK_SIZES.MAX}
|
||||
display={DISPLAY.FLEX}
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
className="gas-display__total-value"
|
||||
>
|
||||
<LoadingHeartBeat estimateUsed={transactionData?.userFeeLevel} />
|
||||
<UserPreferencedCurrencyDisplay
|
||||
type={PRIMARY}
|
||||
key="total-detail-value"
|
||||
value={hexTransactionTotal}
|
||||
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
maxAmount = (
|
||||
<UserPreferencedCurrencyDisplay
|
||||
type={PRIMARY}
|
||||
key="total-max-amount"
|
||||
value={addHexes(
|
||||
draftTransaction.amount.value,
|
||||
hexMaximumTransactionFee,
|
||||
)}
|
||||
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
|
||||
/>
|
||||
);
|
||||
} else if (useNativeCurrencyAsPrimaryCurrency) {
|
||||
detailTotal = primaryTotalTextOverrideMaxAmount;
|
||||
maxAmount = primaryTotalTextOverrideMaxAmount;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{showDepositPopover && (
|
||||
<DepositPopover onClose={() => setShowDepositPopover(false)} />
|
||||
)}
|
||||
<Box className="gas-display">
|
||||
<TransactionDetail
|
||||
userAcknowledgedGasMissing={false}
|
||||
rows={[
|
||||
supportsEIP1559V2 ? (
|
||||
<TransactionDetailItem
|
||||
key="gas-item"
|
||||
detailTitle={
|
||||
<Box display={DISPLAY.FLEX}>
|
||||
<Box marginRight={1}>{t('gas')}</Box>
|
||||
<Typography
|
||||
as="span"
|
||||
marginTop={0}
|
||||
color={COLORS.TEXT_MUTED}
|
||||
fontStyle={FONT_STYLE.ITALIC}
|
||||
fontWeight={FONT_WEIGHT.NORMAL}
|
||||
className="gas-display__title__estimate"
|
||||
>
|
||||
({t('transactionDetailGasInfoV2')})
|
||||
</Typography>
|
||||
<InfoTooltip
|
||||
contentText={
|
||||
<>
|
||||
<Typography variant={TYPOGRAPHY.H7}>
|
||||
{t('transactionDetailGasTooltipIntro', [
|
||||
isMainnet ? t('networkNameEthereum') : '',
|
||||
])}
|
||||
</Typography>
|
||||
<Typography variant={TYPOGRAPHY.H7}>
|
||||
{t('transactionDetailGasTooltipExplanation')}
|
||||
</Typography>
|
||||
<Typography variant={TYPOGRAPHY.H7}>
|
||||
<a
|
||||
href="https://community.metamask.io/t/what-is-gas-why-do-transactions-take-so-long/3172"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t('transactionDetailGasTooltipConversion')}
|
||||
</a>
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
position="right"
|
||||
/>
|
||||
</Box>
|
||||
}
|
||||
detailTitleColor={COLORS.TEXT_DEFAULT}
|
||||
detailText={
|
||||
<Box className="gas-display__currency-container">
|
||||
<LoadingHeartBeat estimateUsed={estimateUsed} />
|
||||
<UserPreferencedCurrencyDisplay
|
||||
type={SECONDARY}
|
||||
value={hexMinimumTransactionFee}
|
||||
hideLabel={Boolean(useNativeCurrencyAsPrimaryCurrency)}
|
||||
/>
|
||||
</Box>
|
||||
}
|
||||
detailTotal={
|
||||
<Box className="gas-display__currency-container">
|
||||
<LoadingHeartBeat estimateUsed={estimateUsed} />
|
||||
<UserPreferencedCurrencyDisplay
|
||||
type={PRIMARY}
|
||||
value={hexMinimumTransactionFee}
|
||||
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
|
||||
/>
|
||||
</Box>
|
||||
}
|
||||
subText={
|
||||
<>
|
||||
<Box
|
||||
key="editGasSubTextFeeLabel"
|
||||
display={DISPLAY.INLINE_FLEX}
|
||||
className={classNames('gas-display__gas-fee-label', {
|
||||
'gas-display__gas-fee-warning': estimateUsed === 'high',
|
||||
})}
|
||||
>
|
||||
<LoadingHeartBeat estimateUsed={estimateUsed} />
|
||||
<Box marginRight={1}>
|
||||
<strong>
|
||||
{estimateUsed === 'high' && '⚠ '}
|
||||
{t('editGasSubTextFeeLabel')}
|
||||
</strong>
|
||||
</Box>
|
||||
<Box
|
||||
key="editGasSubTextFeeValue"
|
||||
className="gas-display__currency-container"
|
||||
>
|
||||
<LoadingHeartBeat estimateUsed={estimateUsed} />
|
||||
<UserPreferencedCurrencyDisplay
|
||||
key="editGasSubTextFeeAmount"
|
||||
type={PRIMARY}
|
||||
value={hexMaximumTransactionFee}
|
||||
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
}
|
||||
subTitle={
|
||||
<GasTiming
|
||||
maxPriorityFeePerGas={hexWEIToDecGWEI(
|
||||
draftTransaction.gas.maxPriorityFeePerGas,
|
||||
)}
|
||||
maxFeePerGas={hexWEIToDecGWEI(
|
||||
draftTransaction.gas.maxFeePerGas,
|
||||
)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<TransactionDetailItem
|
||||
key="gas-item"
|
||||
detailTitle={
|
||||
<>
|
||||
{t('transactionDetailGasHeading')}
|
||||
<InfoTooltip
|
||||
contentText={
|
||||
<>
|
||||
<p>
|
||||
{t('transactionDetailGasTooltipIntro', [
|
||||
isMainnet ? t('networkNameEthereum') : '',
|
||||
])}
|
||||
</p>
|
||||
<p>{t('transactionDetailGasTooltipExplanation')}</p>
|
||||
<p>
|
||||
<a
|
||||
href="https://community.metamask.io/t/what-is-gas-why-do-transactions-take-so-long/3172"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t('transactionDetailGasTooltipConversion')}
|
||||
</a>
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
position="right"
|
||||
>
|
||||
<i className="fa fa-info-circle" />
|
||||
</InfoTooltip>
|
||||
</>
|
||||
}
|
||||
detailText={
|
||||
<Box
|
||||
height={BLOCK_SIZES.MAX}
|
||||
display={DISPLAY.FLEX}
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
className="gas-display__currency-container"
|
||||
>
|
||||
<LoadingHeartBeat
|
||||
estimateUsed={transactionData?.userFeeLevel}
|
||||
/>
|
||||
<UserPreferencedCurrencyDisplay
|
||||
type={SECONDARY}
|
||||
value={hexMinimumTransactionFee}
|
||||
hideLabel={Boolean(useNativeCurrencyAsPrimaryCurrency)}
|
||||
/>
|
||||
</Box>
|
||||
}
|
||||
detailTotal={
|
||||
<Box
|
||||
height={BLOCK_SIZES.MAX}
|
||||
display={DISPLAY.FLEX}
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
className="gas-display__currency-container"
|
||||
>
|
||||
<LoadingHeartBeat
|
||||
estimateUsed={transactionData?.userFeeLevel}
|
||||
/>
|
||||
<UserPreferencedCurrencyDisplay
|
||||
type={PRIMARY}
|
||||
value={hexMinimumTransactionFee}
|
||||
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
|
||||
numberOfDecimals={6}
|
||||
/>
|
||||
</Box>
|
||||
}
|
||||
subText={
|
||||
<>
|
||||
<strong key="editGasSubTextFeeLabel">
|
||||
{t('editGasSubTextFeeLabel')}
|
||||
</strong>
|
||||
<Box
|
||||
height={BLOCK_SIZES.MAX}
|
||||
display={DISPLAY.FLEX}
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
className="gas-display__currency-container"
|
||||
>
|
||||
<LoadingHeartBeat
|
||||
estimateUsed={
|
||||
transactionData?.userFeeLevel ?? estimateUsed
|
||||
}
|
||||
/>
|
||||
<UserPreferencedCurrencyDisplay
|
||||
key="editGasSubTextFeeAmount"
|
||||
type={PRIMARY}
|
||||
value={hexMaximumTransactionFee}
|
||||
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
}
|
||||
subTitle={
|
||||
<>
|
||||
<GasTiming
|
||||
maxPriorityFeePerGas={hexWEIToDecGWEI(
|
||||
draftTransaction.gas.maxPriorityFeePerGas,
|
||||
)}
|
||||
maxFeePerGas={hexWEIToDecGWEI(
|
||||
draftTransaction.gas.maxFeePerGas,
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
),
|
||||
(gasError || isInsufficientTokenError) && (
|
||||
<TransactionDetailItem
|
||||
key="total-item"
|
||||
detailTitle={t('total')}
|
||||
detailText={
|
||||
<Box
|
||||
height={BLOCK_SIZES.MAX}
|
||||
display={DISPLAY.FLEX}
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
className="gas-display__total-value"
|
||||
>
|
||||
<LoadingHeartBeat
|
||||
estimateUsed={transactionData?.userFeeLevel}
|
||||
/>
|
||||
<UserPreferencedCurrencyDisplay
|
||||
type={SECONDARY}
|
||||
key="total-detail-text"
|
||||
value={hexTransactionTotal}
|
||||
hideLabel={Boolean(useNativeCurrencyAsPrimaryCurrency)}
|
||||
/>
|
||||
</Box>
|
||||
}
|
||||
detailTotal={detailTotal}
|
||||
subTitle={t('transactionDetailGasTotalSubtitle')}
|
||||
subText={
|
||||
<Box
|
||||
height={BLOCK_SIZES.MAX}
|
||||
display={DISPLAY.FLEX}
|
||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||
className="gas-display__total-amount"
|
||||
>
|
||||
<LoadingHeartBeat
|
||||
estimateUsed={
|
||||
transactionData?.userFeeLevel ?? estimateUsed
|
||||
}
|
||||
/>
|
||||
<strong key="editGasSubTextAmountLabel">
|
||||
{t('editGasSubTextAmountLabel')}
|
||||
</strong>{' '}
|
||||
{maxAmount}
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
),
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
{(gasError || isInsufficientTokenError) && (
|
||||
<Box className="gas-display__warning-message">
|
||||
<Box
|
||||
paddingTop={0}
|
||||
paddingRight={4}
|
||||
paddingBottom={4}
|
||||
paddingLeft={4}
|
||||
className="gas-display__confirm-approve-content__warning"
|
||||
>
|
||||
<ActionableMessage
|
||||
message={
|
||||
isBuyableChain && draftTransaction.asset.type === 'NATIVE' ? (
|
||||
<Typography variant={TYPOGRAPHY.H7} align="left">
|
||||
{t('insufficientCurrencyBuyOrReceive', [
|
||||
nativeCurrency,
|
||||
networkName ?? currentProvider.nickname,
|
||||
<Button
|
||||
type="inline"
|
||||
className="confirm-page-container-content__link"
|
||||
onClick={() => {
|
||||
setShowDepositPopover(true);
|
||||
}}
|
||||
key={`${nativeCurrency}-buy-button`}
|
||||
>
|
||||
{t('buyAsset', [nativeCurrency])}
|
||||
</Button>,
|
||||
<Button
|
||||
type="inline"
|
||||
className="gas-display__link"
|
||||
onClick={() =>
|
||||
dispatch(showModal({ name: 'ACCOUNT_DETAILS' }))
|
||||
}
|
||||
key="receive-button"
|
||||
>
|
||||
{t('deposit')}
|
||||
</Button>,
|
||||
])}
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography variant={TYPOGRAPHY.H7} align="left">
|
||||
{t('insufficientCurrencyBuyOrReceive', [
|
||||
draftTransaction.asset.details?.symbol ?? nativeCurrency,
|
||||
networkName ?? currentProvider.nickname,
|
||||
`${t('buyAsset', [
|
||||
draftTransaction.asset.details?.symbol ??
|
||||
nativeCurrency,
|
||||
])}`,
|
||||
<Button
|
||||
type="inline"
|
||||
className="gas-display__link"
|
||||
onClick={() =>
|
||||
dispatch(showModal({ name: 'ACCOUNT_DETAILS' }))
|
||||
}
|
||||
key="receive-button"
|
||||
>
|
||||
{t('deposit')}
|
||||
</Button>,
|
||||
])}
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
useIcon
|
||||
iconFillColor="var(--color-error-default)"
|
||||
type="danger"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
GasDisplay.propTypes = {
|
||||
gasError: PropTypes.string,
|
||||
};
|
1
ui/pages/send/gas-display/index.js
Normal file
1
ui/pages/send/gas-display/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './gas-display';
|
58
ui/pages/send/gas-display/index.scss
Normal file
58
ui/pages/send/gas-display/index.scss
Normal file
@ -0,0 +1,58 @@
|
||||
.gas-display {
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
|
||||
.transaction-detail-rows {
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--color-border-default);
|
||||
margin: 16px 16px;
|
||||
|
||||
.transaction-detail-item {
|
||||
&:not(:first-child) {
|
||||
border-top: 1px solid var(--color-border-default);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
&__estimate {
|
||||
font-size: 12px;
|
||||
line-height: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
&__gas-fee-warning {
|
||||
color: var(--color-warning-default);
|
||||
}
|
||||
|
||||
&__gas-fee-label {
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__warning-message {
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
&__currency-container,
|
||||
&__total-amount,
|
||||
&__total-value {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__confirm-approve-content {
|
||||
&__warning {
|
||||
@media screen and (max-width: $break-small) {
|
||||
padding: 0 32px 16px 16px;
|
||||
position: fixed;
|
||||
bottom: 80px;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__link {
|
||||
text-transform: lowercase;
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ import {
|
||||
} from '../../../helpers/constants/error-keys';
|
||||
import { ASSET_TYPES } from '../../../../shared/constants/transaction';
|
||||
import { CONTRACT_ADDRESS_LINK } from '../../../helpers/constants/common';
|
||||
import GasDisplay from '../gas-display';
|
||||
import SendAmountRow from './send-amount-row';
|
||||
import SendHexDataRow from './send-hex-data-row';
|
||||
import SendAssetRow from './send-asset-row';
|
||||
@ -81,7 +82,6 @@ export default class SendContent extends Component {
|
||||
<PageContainerContent>
|
||||
<div className="send-v2__form">
|
||||
{assetError ? this.renderError(assetError) : null}
|
||||
{gasError ? this.renderError(gasError) : null}
|
||||
{isEthGasPrice
|
||||
? this.renderWarning(ETH_GAS_PRICE_FETCH_WARNING_KEY)
|
||||
: null}
|
||||
@ -97,6 +97,7 @@ export default class SendContent extends Component {
|
||||
<SendAmountRow />
|
||||
{networkOrAccountNotSupports1559 ? <SendGasRow /> : null}
|
||||
{showHexData ? <SendHexDataRow /> : null}
|
||||
<GasDisplay gasError={gasError} />
|
||||
</div>
|
||||
</PageContainerContent>
|
||||
);
|
||||
|
@ -150,21 +150,6 @@ describe('SendContent Component', () => {
|
||||
).toStrictEqual(true);
|
||||
expect(wrapper.find(Dialog)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should render insufficient gas dialog', () => {
|
||||
wrapper.setProps({
|
||||
showHexData: false,
|
||||
getIsBalanceInsufficient: true,
|
||||
});
|
||||
const PageContainerContentChild = wrapper
|
||||
.find(PageContainerContent)
|
||||
.children();
|
||||
const errorDialogProps = PageContainerContentChild.childAt(0).props();
|
||||
expect(errorDialogProps.className).toStrictEqual('send__error-dialog');
|
||||
expect(errorDialogProps.children).toStrictEqual(
|
||||
'insufficientFundsForGas_t',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not render the asset dropdown if token length is 0', () => {
|
||||
|
@ -15,7 +15,6 @@ import {
|
||||
acknowledgeRecipientWarning,
|
||||
getRecipientWarningAcknowledgement,
|
||||
} from '../../../ducks/send';
|
||||
|
||||
import SendContent from './send-content.component';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
@ -24,6 +23,7 @@ function mapStateToProps(state) {
|
||||
const recipient = getRecipient(state);
|
||||
const recipientWarningAcknowledged =
|
||||
getRecipientWarningAcknowledgement(state);
|
||||
|
||||
return {
|
||||
isOwnedAccount: Boolean(
|
||||
ownedAccounts.find(
|
||||
|
@ -31,6 +31,7 @@ const TOKEN_TRANSFER_FUNCTION_SIGNATURE = '0xa9059cbb';
|
||||
const COLLECTIBLE_TRANSFER_FROM_FUNCTION_SIGNATURE = '0x23b872dd';
|
||||
|
||||
const INSUFFICIENT_FUNDS_ERROR = 'insufficientFunds';
|
||||
const INSUFFICIENT_FUNDS_FOR_GAS_ERROR = 'insufficientFundsForGas';
|
||||
const INSUFFICIENT_TOKENS_ERROR = 'insufficientTokens';
|
||||
const NEGATIVE_ETH_ERROR = 'negativeETH';
|
||||
const INVALID_RECIPIENT_ADDRESS_ERROR = 'invalidAddressRecipient';
|
||||
@ -56,6 +57,7 @@ export {
|
||||
MAX_GAS_LIMIT_DEC,
|
||||
HIGH_FEE_WARNING_MULTIPLIER,
|
||||
INSUFFICIENT_FUNDS_ERROR,
|
||||
INSUFFICIENT_FUNDS_FOR_GAS_ERROR,
|
||||
INSUFFICIENT_TOKENS_ERROR,
|
||||
INVALID_RECIPIENT_ADDRESS_ERROR,
|
||||
KNOWN_RECIPIENT_ADDRESS_WARNING,
|
||||
|
@ -4,8 +4,11 @@ import thunk from 'redux-thunk';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { SEND_STAGES, startNewDraftTransaction } from '../../ducks/send';
|
||||
import { domainInitialState } from '../../ducks/domains';
|
||||
import { renderWithProvider } from '../../../test/jest';
|
||||
import { CHAIN_IDS } from '../../../shared/constants/network';
|
||||
import {
|
||||
renderWithProvider,
|
||||
setBackgroundConnection,
|
||||
} from '../../../test/jest';
|
||||
import { GAS_ESTIMATE_TYPES } from '../../../shared/constants/gas';
|
||||
import { KEYRING_TYPES } from '../../../shared/constants/keyrings';
|
||||
import { INITIAL_SEND_STATE_FOR_EXISTING_DRAFT } from '../../../test/jest/mocks';
|
||||
@ -38,6 +41,12 @@ jest.mock('react-router-dom', () => {
|
||||
};
|
||||
});
|
||||
|
||||
setBackgroundConnection({
|
||||
getGasFeeTimeEstimate: jest.fn(),
|
||||
getGasFeeEstimatesAndStartPolling: jest.fn(),
|
||||
promisifiedBackground: jest.fn(),
|
||||
});
|
||||
|
||||
jest.mock('ethers', () => {
|
||||
const originalModule = jest.requireActual('ethers');
|
||||
return {
|
||||
@ -60,6 +69,14 @@ const baseStore = {
|
||||
},
|
||||
history: { mostRecentOverviewPage: 'activity' },
|
||||
metamask: {
|
||||
unapprovedTxs: {
|
||||
1: {
|
||||
id: 1,
|
||||
txParams: {
|
||||
value: 'oldTxValue',
|
||||
},
|
||||
},
|
||||
},
|
||||
gasEstimateType: GAS_ESTIMATE_TYPES.LEGACY,
|
||||
gasFeeEstimates: {
|
||||
low: '0',
|
||||
@ -204,6 +221,27 @@ describe('Send Page', () => {
|
||||
const store = configureMockStore(middleware)({
|
||||
...baseStore,
|
||||
send: { ...baseStore.send, stage: SEND_STAGES.DRAFT },
|
||||
confirmTransaction: {
|
||||
txData: {
|
||||
id: 3111025347726181,
|
||||
time: 1620723786838,
|
||||
status: 'unapproved',
|
||||
metamaskNetworkId: '5',
|
||||
chainId: '0x5',
|
||||
loadingDefaults: false,
|
||||
txParams: {
|
||||
from: '0x64a845a5b02460acf8a3d84503b0d68d028b4bb4',
|
||||
to: '0xaD6D458402F60fD3Bd25163575031ACDce07538D',
|
||||
value: '0x0',
|
||||
data: '0x095ea7b30000000000000000000000009bc5baf874d2da8d216ae9f137804184ee5afef40000000000000000000000000000000000000000000000000000000000011170',
|
||||
gas: '0xea60',
|
||||
gasPrice: '0x4a817c800',
|
||||
},
|
||||
type: 'transfer',
|
||||
origin: 'https://metamask.github.io',
|
||||
transactionCategory: 'approve',
|
||||
},
|
||||
},
|
||||
});
|
||||
const { getByText } = renderWithProvider(<Send />, store);
|
||||
expect(getByText('Send')).toBeTruthy();
|
||||
@ -221,6 +259,27 @@ describe('Send Page', () => {
|
||||
const store = configureMockStore(middleware)({
|
||||
...baseStore,
|
||||
send: { ...baseStore.send, stage: SEND_STAGES.DRAFT },
|
||||
confirmTransaction: {
|
||||
txData: {
|
||||
id: 3111025347726181,
|
||||
time: 1620723786838,
|
||||
status: 'unapproved',
|
||||
metamaskNetworkId: '5',
|
||||
chainId: '0x5',
|
||||
loadingDefaults: false,
|
||||
txParams: {
|
||||
from: '0x64a845a5b02460acf8a3d84503b0d68d028b4bb4',
|
||||
to: '0xaD6D458402F60fD3Bd25163575031ACDce07538D',
|
||||
value: '0x0',
|
||||
data: '0x095ea7b30000000000000000000000009bc5baf874d2da8d216ae9f137804184ee5afef40000000000000000000000000000000000000000000000000000000000011170',
|
||||
gas: '0xea60',
|
||||
gasPrice: '0x4a817c800',
|
||||
},
|
||||
type: 'transfer',
|
||||
origin: 'https://metamask.github.io',
|
||||
transactionCategory: 'approve',
|
||||
},
|
||||
},
|
||||
});
|
||||
const { getByText } = renderWithProvider(<Send />, store);
|
||||
expect(getByText('Next')).toBeTruthy();
|
||||
|
Loading…
Reference in New Issue
Block a user