Skip to content
Merged
Next Next commit
fix ui flickering gas station
  • Loading branch information
vinistevam committed Nov 26, 2025
commit 9cacf14f13920fcb53642f13898dda08cd1ed44b
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { GasFeeTokenModal } from '../gas-fee-token-modal';
import { useSelectedGasFeeToken } from '../../hooks/useGasFeeToken';
import { GasFeeTokenIcon, GasFeeTokenIconSize } from '../gas-fee-token-icon';
import { useIsGaslessSupported } from '../../../../../hooks/gas/useIsGaslessSupported';
import { useIsInsufficientBalance } from '../../../../../hooks/useIsInsufficientBalance';
import { useHasInsufficientBalance } from '../../../../../hooks/useHasInsufficientBalance';

// TODO: Fix in https://github.com/MetaMask/metamask-extension/issues/31860
// eslint-disable-next-line @typescript-eslint/naming-convention
Expand All @@ -36,7 +36,7 @@ export function SelectedGasFeeToken() {
const { isSupported: isGaslessSupported, isSmartTransaction } =
useIsGaslessSupported();

const hasInsufficientNative = useIsInsufficientBalance();
const hasInsufficientNative = useHasInsufficientBalance();

const hasOnlyFutureNativeToken =
gasFeeTokens?.length === 1 &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ describe('useInsufficientBalanceAlerts', () => {
useIsGaslessSupportedMock.mockReturnValue({
isSmartTransaction: false,
isSupported: false,
pending: false,
});
});

Expand All @@ -117,6 +118,7 @@ describe('useInsufficientBalanceAlerts', () => {
useIsGaslessSupportedMock.mockReturnValue({
isSmartTransaction: false,
isSupported: true,
pending: false,
});

const alerts = runHook({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,18 @@
import { TransactionMeta } from '@metamask/transaction-controller';
import { CaipChainId, Hex } from '@metamask/utils';
import { useMemo } from 'react';
import { useSelector } from 'react-redux';

import { sumHexes } from '../../../../../../shared/modules/conversion.utils';
import {
AlertActionKey,
RowAlertKey,
} from '../../../../../components/app/confirm/info/row/constants';
import { Alert } from '../../../../../ducks/confirm-alerts/confirm-alerts';
import { Severity } from '../../../../../helpers/constants/design-system';
import { useI18nContext } from '../../../../../hooks/useI18nContext';
import {
getMultichainNetworkConfigurationsByChainId,
getNativeTokenCachedBalanceByChainIdByAccountAddress,
getUseTransactionSimulations,
selectTransactionFeeById,
} from '../../../../../selectors';
import { getUseTransactionSimulations } from '../../../../../selectors';
import { useConfirmContext } from '../../../context/confirm';
import { isBalanceSufficient } from '../../../send-legacy/send.utils';
import { useIsGaslessSupported } from '../../gas/useIsGaslessSupported';
import { useHasInsufficientBalance } from '../../useHasInsufficientBalance';

export function useInsufficientBalanceAlerts({
ignoreGasFeeToken,
Expand All @@ -28,62 +21,37 @@ export function useInsufficientBalanceAlerts({
} = {}): Alert[] {
const t = useI18nContext();
const { currentConfirmation } = useConfirmContext<TransactionMeta>();
const {
id: transactionId,
chainId,
selectedGasFeeToken,
gasFeeTokens,
txParams: { value = '0x0', from: fromAddress = '' } = {},
} = currentConfirmation ?? {};

const batchTransactionValues =
currentConfirmation?.nestedTransactions?.map(
(trxn) => (trxn.value as Hex) ?? 0x0,
) ?? [];
const { selectedGasFeeToken, gasFeeTokens } = currentConfirmation ?? {};
const { hasInsufficientBalance, nativeCurrency } =
useHasInsufficientBalance();

const isSimulationEnabled = useSelector(getUseTransactionSimulations);

const chainBalances = useSelector((state) =>
getNativeTokenCachedBalanceByChainIdByAccountAddress(
state,
fromAddress ?? '',
),
) as Record<Hex, Hex>;

const balance = chainBalances?.[chainId as Hex] ?? '0x0';

const totalValue = sumHexes(value, ...batchTransactionValues);

const { hexMaximumTransactionFee } = useSelector((state) =>
selectTransactionFeeById(state, transactionId),
);
const isSponsored = currentConfirmation?.isGasFeeSponsored;
const {
isSupported: isGaslessSupported,
pending: isGaslessSupportedPending,
} = useIsGaslessSupported();

const [multichainNetworks, evmNetworks] = useSelector(
getMultichainNetworkConfigurationsByChainId,
);
const isGasFeeTokensEmpty = gasFeeTokens?.length === 0;
const hasNoGasFeeTokenSelected = ignoreGasFeeToken || !selectedGasFeeToken;

const nativeCurrency = (
multichainNetworks[chainId as CaipChainId] ?? evmNetworks[chainId]
)?.nativeCurrency;
const isGaslessCheckComplete =
!isGaslessSupportedPending && isGaslessSupported;

const insufficientBalance = !isBalanceSufficient({
amount: totalValue,
gasTotal: hexMaximumTransactionFee,
balance,
});
const isSponsoredTransaction = isSponsored && isGaslessCheckComplete;

const isSponsored = currentConfirmation?.isGasFeeSponsored;
const { isSupported: isGaslessSupported } = useIsGaslessSupported();
const isSponsoredTransaction = isSponsored && isGaslessSupported;
const isSimulationComplete = !isSimulationEnabled || Boolean(gasFeeTokens);

const canSkipSimulationChecks = ignoreGasFeeToken || !isSimulationEnabled;
const hasGaslessSimulationFinished =
canSkipSimulationChecks || Boolean(gasFeeTokens);
const isGaslessEligibleForAlert =
(isGasFeeTokensEmpty && isGaslessCheckComplete) ||
(!isGasFeeTokensEmpty && !isGaslessCheckComplete);

const showAlert =
insufficientBalance &&
hasGaslessSimulationFinished &&
(ignoreGasFeeToken || !selectedGasFeeToken) &&
hasInsufficientBalance &&
isSimulationComplete &&
hasNoGasFeeTokenSelected &&
isGaslessEligibleForAlert &&
!isSponsoredTransaction;

return useMemo(() => {
Expand Down
12 changes: 8 additions & 4 deletions ui/pages/confirmations/hooks/gas/useIsGaslessLoading.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import { GasFeeToken } from '@metamask/transaction-controller';
import { renderHookWithConfirmContextProvider } from '../../../../../test/lib/confirmations/render-helpers';
import { genUnapprovedContractInteractionConfirmation } from '../../../../../test/data/confirmations/contract-interaction';
import { getMockConfirmStateForTransaction } from '../../../../../test/data/confirmations/helper';
import { useIsInsufficientBalance } from '../useIsInsufficientBalance';
import { useHasInsufficientBalance } from '../useHasInsufficientBalance';
import { useIsGaslessLoading } from './useIsGaslessLoading';
import { useIsGaslessSupported } from './useIsGaslessSupported';

jest.mock('./useIsGaslessSupported');
jest.mock('../useIsInsufficientBalance');
jest.mock('../useHasInsufficientBalance');

const mockedUseIsGaslessSupported = jest.mocked(useIsGaslessSupported);
const mockedUseIsInsufficientBalance = jest.mocked(useIsInsufficientBalance);
const mockedUseHasInsufficientBalance = jest.mocked(useHasInsufficientBalance);

async function runHook({
simulationEnabled,
Expand All @@ -26,8 +26,12 @@ async function runHook({
mockedUseIsGaslessSupported.mockReturnValue({
isSupported: gaslessSupported,
isSmartTransaction: true,
pending: false,
});
mockedUseHasInsufficientBalance.mockReturnValue({
hasInsufficientBalance: insufficientBalance,
nativeCurrency: 'USD',
});
mockedUseIsInsufficientBalance.mockReturnValue(insufficientBalance);

const { result } = renderHookWithConfirmContextProvider(
useIsGaslessLoading,
Expand Down
12 changes: 7 additions & 5 deletions ui/pages/confirmations/hooks/gas/useIsGaslessLoading.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useSelector } from 'react-redux';
import { TransactionMeta } from '@metamask/transaction-controller';
import { useConfirmContext } from '../../context/confirm';
import { getUseTransactionSimulations } from '../../../../selectors';
import { useIsInsufficientBalance } from '../useIsInsufficientBalance';
import { useHasInsufficientBalance } from '../useHasInsufficientBalance';
import { useIsGaslessSupported } from './useIsGaslessSupported';

export function useIsGaslessLoading() {
Expand All @@ -11,15 +11,17 @@ export function useIsGaslessLoading() {

const { gasFeeTokens } = transactionMeta ?? {};

const { isSupported: isGaslessSupported } = useIsGaslessSupported();
const { isSupported: isGaslessSupported, pending } = useIsGaslessSupported();
const isSimulationEnabled = useSelector(getUseTransactionSimulations);

const hasInsufficientNative = useIsInsufficientBalance();
const { hasInsufficientBalance } = useHasInsufficientBalance();

const isGaslessSupportedFinished = !pending && isGaslessSupported;

const isGaslessLoading =
isSimulationEnabled &&
isGaslessSupported &&
hasInsufficientNative &&
isGaslessSupportedFinished &&
hasInsufficientBalance &&
!gasFeeTokens;

return { isGaslessLoading };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ describe('useIsGaslessSupported', () => {
expect(result).toStrictEqual({
isSupported: true,
isSmartTransaction: true,
pending: false,
});
});

Expand All @@ -74,6 +75,7 @@ describe('useIsGaslessSupported', () => {
expect(result).toStrictEqual({
isSupported: true,
isSmartTransaction: false,
pending: false,
});
});

Expand All @@ -85,6 +87,7 @@ describe('useIsGaslessSupported', () => {
expect(result).toStrictEqual({
isSupported: false,
isSmartTransaction: false,
pending: false,
});
});

Expand All @@ -96,6 +99,7 @@ describe('useIsGaslessSupported', () => {
expect(result).toStrictEqual({
isSupported: false,
isSmartTransaction: false,
pending: false,
});
});

Expand All @@ -107,6 +111,7 @@ describe('useIsGaslessSupported', () => {
expect(result).toStrictEqual({
isSupported: false,
isSmartTransaction: false,
pending: false,
});
});
});
Expand All @@ -121,6 +126,7 @@ describe('useIsGaslessSupported', () => {
expect(result).toStrictEqual({
isSupported: false,
isSmartTransaction: true,
pending: false,
});
});

Expand All @@ -142,6 +148,7 @@ describe('useIsGaslessSupported', () => {
expect(result).toStrictEqual({
isSupported: false,
isSmartTransaction: true,
pending: true,
});
});
});
15 changes: 9 additions & 6 deletions ui/pages/confirmations/hooks/gas/useIsGaslessSupported.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { useGaslessSupportedSmartTransactions } from './useGaslessSupportedSmart
* @returns An object containing:
* - `isSupported`: `true` if gasless transactions are supported via either 7702 or smart transactions with sendBundle.
* - `isSmartTransaction`: `true` if smart transactions are enabled for the current chain.
* - `pending`: `true` if the support check is still in progress.
*/
export function useIsGaslessSupported() {
const { currentConfirmation: transactionMeta } =
Expand All @@ -30,13 +31,14 @@ export function useIsGaslessSupported() {
const shouldCheck7702Eligibility =
!pending && !isSmartTransactionAndBundleSupported;

const { value: relaySupportsChain } = useAsyncResult(async () => {
if (!shouldCheck7702Eligibility) {
return undefined;
}
const { value: relaySupportsChain, pending: relayPending } =
useAsyncResult(async () => {
if (!shouldCheck7702Eligibility) {
return undefined;
}

return isRelaySupported(chainId);
}, [chainId, shouldCheck7702Eligibility]);
return isRelaySupported(chainId);
}, [chainId, shouldCheck7702Eligibility]);

const is7702Supported = Boolean(
relaySupportsChain &&
Expand All @@ -51,5 +53,6 @@ export function useIsGaslessSupported() {
return {
isSupported,
isSmartTransaction,
pending: pending || relayPending,
};
}
17 changes: 10 additions & 7 deletions ui/pages/confirmations/hooks/useAutomaticGasFeeTokenSelect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,22 @@ import { useAsyncResult } from '../../../hooks/useAsync';
import { forceUpdateMetamaskState } from '../../../store/actions';
import { updateSelectedGasFeeToken } from '../../../store/controller-actions/transaction-controller';
import { useConfirmContext } from '../context/confirm';
import { useInsufficientBalanceAlerts } from './alerts/transactions/useInsufficientBalanceAlerts';
import { useIsGaslessSupported } from './gas/useIsGaslessSupported';
import { useHasInsufficientBalance } from './useHasInsufficientBalance';

export function useAutomaticGasFeeTokenSelect() {
const dispatch = useDispatch();
const { isSupported: isGaslessSupported, isSmartTransaction } =
useIsGaslessSupported();
const {
isSupported: isGaslessSupported,
isSmartTransaction,
pending,
} = useIsGaslessSupported();
const [firstCheck, setFirstCheck] = useState(true);

const { currentConfirmation: transactionMeta } =
useConfirmContext<TransactionMeta>();

const hasInsufficientBalance = Boolean(
useInsufficientBalanceAlerts()?.length,
);
const { hasInsufficientBalance } = useHasInsufficientBalance();

const {
gasFeeTokens,
Expand All @@ -40,8 +41,10 @@ export function useAutomaticGasFeeTokenSelect() {
await forceUpdateMetamaskState(dispatch);
}, [dispatch, transactionId, firstGasFeeTokenAddress]);

const isGaslessSupportedAndFinished = isGaslessSupported && !pending;

const shouldSelect =
isGaslessSupported &&
isGaslessSupportedAndFinished &&
hasInsufficientBalance &&
!selectedGasFeeToken &&
Boolean(firstGasFeeTokenAddress);
Expand Down
Loading
Loading