diff --git a/ui/components/app/edit-gas-display/edit-gas-display.component.js b/ui/components/app/edit-gas-display/edit-gas-display.component.js index f44e5aa26..2bdef8a9a 100644 --- a/ui/components/app/edit-gas-display/edit-gas-display.component.js +++ b/ui/components/app/edit-gas-display/edit-gas-display.component.js @@ -19,6 +19,7 @@ import { import { areDappSuggestedAndTxParamGasFeesTheSame } from '../../../helpers/utils/confirm-tx.util'; import InfoTooltip from '../../ui/info-tooltip'; +import ErrorMessage from '../../ui/error-message'; import TransactionTotalBanner from '../transaction-total-banner/transaction-total-banner.component'; import RadioGroup from '../../ui/radio-group/radio-group.component'; import AdvancedGasControls from '../advanced-gas-controls/advanced-gas-controls.component'; @@ -59,6 +60,7 @@ export default function EditGasDisplay({ gasErrors, onManualChange, minimumGasLimit, + balanceError, }) { const t = useContext(I18nContext); @@ -73,6 +75,12 @@ export default function EditGasDisplay({ ); const networkSupports1559 = useSelector(isEIP1559Network); + const showTopError = balanceError; + + let errorKey; + if (balanceError) { + errorKey = 'insufficientFunds'; + } return (
@@ -85,6 +93,11 @@ export default function EditGasDisplay({ />
)} + {showTopError && ( +
+ +
+ )} {requireDappAcknowledgement && (
setShowEducationContent(false) : undefined } @@ -191,7 +192,7 @@ export default function EditGasPopover({ @@ -238,6 +239,7 @@ export default function EditGasPopover({ gasErrors={gasErrors} onManualChange={onManualChange} minimumGasLimit={minimumGasLimitDec} + balanceError={balanceError} {...editGasDisplayProps} /> diff --git a/ui/components/app/edit-gas-popover/index.scss b/ui/components/app/edit-gas-popover/index.scss new file mode 100644 index 000000000..c34c68fb7 --- /dev/null +++ b/ui/components/app/edit-gas-popover/index.scss @@ -0,0 +1,7 @@ +.edit-gas-popover { + &__wrapper { + @media screen and (min-width: 576px) { + max-height: 84vh; + } + } +} diff --git a/ui/hooks/useGasFeeInputs.js b/ui/hooks/useGasFeeInputs.js index b56303e74..0312a2123 100644 --- a/ui/hooks/useGasFeeInputs.js +++ b/ui/hooks/useGasFeeInputs.js @@ -11,6 +11,7 @@ import { import { multiplyCurrencies, conversionLessThan, + conversionGreaterThan, } from '../../shared/modules/conversion.utils'; import { getMaximumGasTotalInHexWei, @@ -22,9 +23,14 @@ import { decGWEIToHexWEI, decimalToHex, hexToDecimal, + addHexes, } from '../helpers/utils/conversions.util'; -import { getShouldShowFiat } from '../selectors'; import { GAS_FORM_ERRORS } from '../helpers/constants/gas'; +import { + getShouldShowFiat, + getSelectedAccount, + txDataSelector, +} from '../selectors'; import { useCurrencyDisplay } from './useCurrencyDisplay'; import { useGasFeeEstimates } from './useGasFeeEstimates'; import { useUserPreferencedCurrency } from './useUserPreferencedCurrency'; @@ -165,6 +171,9 @@ export function useGasFeeInputs( minimumGasLimit, editGasMode, ) { + const { balance: ethBalance } = useSelector(getSelectedAccount); + const txData = useSelector(txDataSelector); + // We need to know whether to show fiat conversions or not, so that we can // default our fiat values to empty strings if showing fiat is not wanted or // possible. @@ -413,6 +422,16 @@ export function useGasFeeInputs( ...gasWarnings, }; + const minimumTxCostInHexWei = addHexes( + minimumCostInHexWei, + txData?.txParams?.value, + ); + + const balanceError = conversionGreaterThan( + { value: minimumTxCostInHexWei, fromNumericBase: 'hex' }, + { value: ethBalance, fromNumericBase: 'hex' }, + ); + return { maxFeePerGas: maxFeePerGasToUse, maxFeePerGasFiat: showFiat ? maxFeePerGasFiat : '', @@ -443,5 +462,6 @@ export function useGasFeeInputs( setMaxFeePerGas(maxFeePerGasToUse); setMaxPriorityFeePerGas(maxPriorityFeePerGasToUse); }, + balanceError, }; } diff --git a/ui/hooks/useGasFeeInputs.test.js b/ui/hooks/useGasFeeInputs.test.js index a4b1400bb..96353be8f 100644 --- a/ui/hooks/useGasFeeInputs.test.js +++ b/ui/hooks/useGasFeeInputs.test.js @@ -7,7 +7,12 @@ import { getNativeCurrency, } from '../ducks/metamask/metamask'; import { ETH, PRIMARY } from '../helpers/constants/common'; -import { getCurrentCurrency, getShouldShowFiat } from '../selectors'; +import { + getCurrentCurrency, + getShouldShowFiat, + txDataSelector, + getSelectedAccount, +} from '../selectors'; import { useGasFeeEstimates } from './useGasFeeEstimates'; import { useGasFeeInputs } from './useGasFeeInputs'; import { useUserPreferencedCurrency } from './useUserPreferencedCurrency'; @@ -71,6 +76,32 @@ const FEE_MARKET_ESTIMATE_RETURN_VALUE = { estimatedGasFeeTimeBounds: {}, }; +const HIGH_FEE_MARKET_ESTIMATE_RETURN_VALUE = { + gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET, + gasFeeEstimates: { + low: { + minWaitTimeEstimate: 180000, + maxWaitTimeEstimate: 300000, + suggestedMaxPriorityFeePerGas: '3', + suggestedMaxFeePerGas: '53000', + }, + medium: { + minWaitTimeEstimate: 15000, + maxWaitTimeEstimate: 60000, + suggestedMaxPriorityFeePerGas: '7', + suggestedMaxFeePerGas: '70000', + }, + high: { + minWaitTimeEstimate: 0, + maxWaitTimeEstimate: 15000, + suggestedMaxPriorityFeePerGas: '10', + suggestedMaxFeePerGas: '100000', + }, + estimatedBaseFee: '50000', + }, + estimatedGasFeeTimeBounds: {}, +}; + const generateUseSelectorRouter = () => (selector) => { if (selector === getConversionRate) { return MOCK_ETH_USD_CONVERSION_RATE; @@ -84,6 +115,18 @@ const generateUseSelectorRouter = () => (selector) => { if (selector === getShouldShowFiat) { return true; } + if (selector === txDataSelector) { + return { + txParams: { + value: '0x5555', + }, + }; + } + if (selector === getSelectedAccount) { + return { + balance: '0x440aa47cc2556', + }; + } return undefined; }; @@ -234,4 +277,32 @@ describe('useGasFeeInputs', () => { // expect(result.current.estimatedMinimumFiat).toBe(`$${totalMaxFiat}`); }); }); + + describe('when balance is sufficient for minimum transaction cost', () => { + beforeEach(() => { + useGasFeeEstimates.mockImplementation( + () => FEE_MARKET_ESTIMATE_RETURN_VALUE, + ); + useSelector.mockImplementation(generateUseSelectorRouter()); + }); + + it('should return false', () => { + const { result } = renderHook(() => useGasFeeInputs()); + expect(result.current.balanceError).toBe(false); + }); + }); + + describe('when balance is insufficient for minimum transaction cost', () => { + beforeEach(() => { + useGasFeeEstimates.mockImplementation( + () => HIGH_FEE_MARKET_ESTIMATE_RETURN_VALUE, + ); + useSelector.mockImplementation(generateUseSelectorRouter()); + }); + + it('should return true', () => { + const { result } = renderHook(() => useGasFeeInputs()); + expect(result.current.balanceError).toBe(true); + }); + }); });