From 9cacf14f13920fcb53642f13898dda08cd1ed44b Mon Sep 17 00:00:00 2001 From: Vinicius Stevam Date: Wed, 26 Nov 2025 11:39:52 +0000 Subject: [PATCH 1/5] fix ui flickering gas station --- .../selected-gas-fee-token.tsx | 4 +- .../useInsufficientBalanceAlerts.test.ts | 2 + .../useInsufficientBalanceAlerts.ts | 78 ++++-------- .../hooks/gas/useIsGaslessLoading.test.ts | 12 +- .../hooks/gas/useIsGaslessLoading.ts | 12 +- .../hooks/gas/useIsGaslessSupported.test.ts | 7 ++ .../hooks/gas/useIsGaslessSupported.ts | 15 ++- .../hooks/useAutomaticGasFeeTokenSelect.ts | 17 +-- .../hooks/useHasInsufficientBalance.test.ts | 115 ++++++++++++++++++ .../hooks/useHasInsufficientBalance.ts | 57 +++++++++ 10 files changed, 240 insertions(+), 79 deletions(-) create mode 100644 ui/pages/confirmations/hooks/useHasInsufficientBalance.test.ts create mode 100644 ui/pages/confirmations/hooks/useHasInsufficientBalance.ts diff --git a/ui/pages/confirmations/components/confirm/info/shared/selected-gas-fee-token/selected-gas-fee-token.tsx b/ui/pages/confirmations/components/confirm/info/shared/selected-gas-fee-token/selected-gas-fee-token.tsx index e3380bc0d099..969b6aa2d564 100644 --- a/ui/pages/confirmations/components/confirm/info/shared/selected-gas-fee-token/selected-gas-fee-token.tsx +++ b/ui/pages/confirmations/components/confirm/info/shared/selected-gas-fee-token/selected-gas-fee-token.tsx @@ -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 @@ -36,7 +36,7 @@ export function SelectedGasFeeToken() { const { isSupported: isGaslessSupported, isSmartTransaction } = useIsGaslessSupported(); - const hasInsufficientNative = useIsInsufficientBalance(); + const hasInsufficientNative = useHasInsufficientBalance(); const hasOnlyFutureNativeToken = gasFeeTokens?.length === 1 && diff --git a/ui/pages/confirmations/hooks/alerts/transactions/useInsufficientBalanceAlerts.test.ts b/ui/pages/confirmations/hooks/alerts/transactions/useInsufficientBalanceAlerts.test.ts index e21a6ccf756b..18beeaf34f93 100644 --- a/ui/pages/confirmations/hooks/alerts/transactions/useInsufficientBalanceAlerts.test.ts +++ b/ui/pages/confirmations/hooks/alerts/transactions/useInsufficientBalanceAlerts.test.ts @@ -106,6 +106,7 @@ describe('useInsufficientBalanceAlerts', () => { useIsGaslessSupportedMock.mockReturnValue({ isSmartTransaction: false, isSupported: false, + pending: false, }); }); @@ -117,6 +118,7 @@ describe('useInsufficientBalanceAlerts', () => { useIsGaslessSupportedMock.mockReturnValue({ isSmartTransaction: false, isSupported: true, + pending: false, }); const alerts = runHook({ diff --git a/ui/pages/confirmations/hooks/alerts/transactions/useInsufficientBalanceAlerts.ts b/ui/pages/confirmations/hooks/alerts/transactions/useInsufficientBalanceAlerts.ts index 3b5ac0fc1ef5..60aea461bfe9 100644 --- a/ui/pages/confirmations/hooks/alerts/transactions/useInsufficientBalanceAlerts.ts +++ b/ui/pages/confirmations/hooks/alerts/transactions/useInsufficientBalanceAlerts.ts @@ -1,9 +1,7 @@ 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, @@ -11,15 +9,10 @@ import { 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, @@ -28,62 +21,37 @@ export function useInsufficientBalanceAlerts({ } = {}): Alert[] { const t = useI18nContext(); const { currentConfirmation } = useConfirmContext(); - 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; - - 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(() => { diff --git a/ui/pages/confirmations/hooks/gas/useIsGaslessLoading.test.ts b/ui/pages/confirmations/hooks/gas/useIsGaslessLoading.test.ts index 9ec35e20e78b..f4d29d051e03 100644 --- a/ui/pages/confirmations/hooks/gas/useIsGaslessLoading.test.ts +++ b/ui/pages/confirmations/hooks/gas/useIsGaslessLoading.test.ts @@ -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, @@ -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, diff --git a/ui/pages/confirmations/hooks/gas/useIsGaslessLoading.ts b/ui/pages/confirmations/hooks/gas/useIsGaslessLoading.ts index 8b5a0296cd91..be91cc1d7a66 100644 --- a/ui/pages/confirmations/hooks/gas/useIsGaslessLoading.ts +++ b/ui/pages/confirmations/hooks/gas/useIsGaslessLoading.ts @@ -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() { @@ -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 }; diff --git a/ui/pages/confirmations/hooks/gas/useIsGaslessSupported.test.ts b/ui/pages/confirmations/hooks/gas/useIsGaslessSupported.test.ts index 7efed5971370..8b0fae932b6e 100644 --- a/ui/pages/confirmations/hooks/gas/useIsGaslessSupported.test.ts +++ b/ui/pages/confirmations/hooks/gas/useIsGaslessSupported.test.ts @@ -62,6 +62,7 @@ describe('useIsGaslessSupported', () => { expect(result).toStrictEqual({ isSupported: true, isSmartTransaction: true, + pending: false, }); }); @@ -74,6 +75,7 @@ describe('useIsGaslessSupported', () => { expect(result).toStrictEqual({ isSupported: true, isSmartTransaction: false, + pending: false, }); }); @@ -85,6 +87,7 @@ describe('useIsGaslessSupported', () => { expect(result).toStrictEqual({ isSupported: false, isSmartTransaction: false, + pending: false, }); }); @@ -96,6 +99,7 @@ describe('useIsGaslessSupported', () => { expect(result).toStrictEqual({ isSupported: false, isSmartTransaction: false, + pending: false, }); }); @@ -107,6 +111,7 @@ describe('useIsGaslessSupported', () => { expect(result).toStrictEqual({ isSupported: false, isSmartTransaction: false, + pending: false, }); }); }); @@ -121,6 +126,7 @@ describe('useIsGaslessSupported', () => { expect(result).toStrictEqual({ isSupported: false, isSmartTransaction: true, + pending: false, }); }); @@ -142,6 +148,7 @@ describe('useIsGaslessSupported', () => { expect(result).toStrictEqual({ isSupported: false, isSmartTransaction: true, + pending: true, }); }); }); diff --git a/ui/pages/confirmations/hooks/gas/useIsGaslessSupported.ts b/ui/pages/confirmations/hooks/gas/useIsGaslessSupported.ts index 2c8cccceb37e..3631d3cae16d 100644 --- a/ui/pages/confirmations/hooks/gas/useIsGaslessSupported.ts +++ b/ui/pages/confirmations/hooks/gas/useIsGaslessSupported.ts @@ -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 } = @@ -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 && @@ -51,5 +53,6 @@ export function useIsGaslessSupported() { return { isSupported, isSmartTransaction, + pending: pending || relayPending, }; } diff --git a/ui/pages/confirmations/hooks/useAutomaticGasFeeTokenSelect.ts b/ui/pages/confirmations/hooks/useAutomaticGasFeeTokenSelect.ts index 6f0904133969..176a20232843 100644 --- a/ui/pages/confirmations/hooks/useAutomaticGasFeeTokenSelect.ts +++ b/ui/pages/confirmations/hooks/useAutomaticGasFeeTokenSelect.ts @@ -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(); - const hasInsufficientBalance = Boolean( - useInsufficientBalanceAlerts()?.length, - ); + const { hasInsufficientBalance } = useHasInsufficientBalance(); const { gasFeeTokens, @@ -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); diff --git a/ui/pages/confirmations/hooks/useHasInsufficientBalance.test.ts b/ui/pages/confirmations/hooks/useHasInsufficientBalance.test.ts new file mode 100644 index 000000000000..21dfabc0b559 --- /dev/null +++ b/ui/pages/confirmations/hooks/useHasInsufficientBalance.test.ts @@ -0,0 +1,115 @@ +import { TransactionMeta, TransactionParams, TransactionType } from '@metamask/transaction-controller'; +import { useHasInsufficientBalance } from './useHasInsufficientBalance'; +import { renderHookWithConfirmContextProvider } from '../../../../test/lib/confirmations/render-helpers'; +import { getMockConfirmState } from '../../../../test/data/confirmations/helper'; +import { ApprovalType, toHex } from '@metamask/controller-utils'; +import { genUnapprovedContractInteractionConfirmation } from '../../../../test/data/confirmations/contract-interaction'; + +const TRANSACTION_ID_MOCK = '123-456'; +const TRANSACTION_MOCK = { + ...genUnapprovedContractInteractionConfirmation({ + chainId: '0x5', + }), + id: TRANSACTION_ID_MOCK, + txParams: { + from: '0x123', + value: '0x2', + maxFeePerGas: '0x2', + gas: '0x3', + } as TransactionParams, +} as TransactionMeta; + +function buildState({ + balance, + currentConfirmation = TRANSACTION_MOCK, + transaction = TRANSACTION_MOCK, + selectedNetworkClientId, + chainId, +}: { + balance?: number; + currentConfirmation?: Partial; + transaction?: Partial; + selectedNetworkClientId?: string; + chainId?: string; +} = {}) { + const accountAddress = transaction?.txParams?.from as string; + + let pendingApprovals = {}; + if (currentConfirmation) { + pendingApprovals = { + [currentConfirmation.id as string]: { + id: currentConfirmation.id, + type: ApprovalType.Transaction, + }, + }; + } + + return getMockConfirmState({ + metamask: { + selectedNetworkClientId: selectedNetworkClientId ?? 'goerli', + pendingApprovals, + accountsByChainId: { + [chainId ?? '0x5']: { + [accountAddress]: { balance: toHex(balance ?? 0) }, + }, + }, + transactions: transaction ? [transaction] : [], + }, + }); +} + +function runHook( + stateOptions?: Parameters[0], +) { + const state = buildState(stateOptions); + const response = renderHookWithConfirmContextProvider( + () => useHasInsufficientBalance(), + state, + ); + + return response.result.current; +} + +describe('useHasInsufficientBalance', () => { + it('returns false if balance sufficient for value + fee', () => { + const result = runHook({ balance: 900000000000 }); + expect(result.hasInsufficientBalance).toBe(false); + expect(result.nativeCurrency).toBe('ETH'); + }); + + it('returns true if balance insufficient for value + fee', () => { + const result = runHook({ balance: 0 }); + expect(result.hasInsufficientBalance).toBe(true); + }); + + it('sums nested transaction values correctly', () => { + const BATCH_TRANSACTION_MOCK = { + ...TRANSACTION_MOCK, + nestedTransactions: [ + { + to: '0x1234567890123456789012345678901234567890', + value: '0x3B9ACA00', + type: TransactionType.simpleSend, + }, + { + to: '0x1234567890123456789012345678901234567891', + value: '0x1DCD6500', + type: TransactionType.simpleSend, + }, + ], + }; + const result = runHook({ currentConfirmation: BATCH_TRANSACTION_MOCK as Partial, + transaction: BATCH_TRANSACTION_MOCK as Partial, balance: 0x10}); + expect(result.hasInsufficientBalance).toBe(true); + }); + + it('returns nativeCurrency from evmNetworks if multichain missing', () => { + const result = runHook(); + expect(result.nativeCurrency).toBe('ETH'); + }); + + it('returns 0x0 if balance missing', () => { + const result = runHook({ balance: undefined }); + expect(result.hasInsufficientBalance).toBe(true); + }); +}); diff --git a/ui/pages/confirmations/hooks/useHasInsufficientBalance.ts b/ui/pages/confirmations/hooks/useHasInsufficientBalance.ts new file mode 100644 index 000000000000..818e1d9b3c6c --- /dev/null +++ b/ui/pages/confirmations/hooks/useHasInsufficientBalance.ts @@ -0,0 +1,57 @@ +import { TransactionMeta } from '@metamask/transaction-controller'; +import { CaipChainId, Hex } from '@metamask/utils'; +import { useSelector } from 'react-redux'; + +import { sumHexes } from '../../../../shared/modules/conversion.utils'; +import { + getMultichainNetworkConfigurationsByChainId, + getNativeTokenCachedBalanceByChainIdByAccountAddress, + selectTransactionFeeById, +} from '../../../selectors'; +import { useConfirmContext } from '../context/confirm'; +import { isBalanceSufficient } from '../send-legacy/send.utils'; + +export function useHasInsufficientBalance(): { hasInsufficientBalance: boolean; nativeCurrency?: string } { + const { currentConfirmation } = useConfirmContext(); + const { + id: transactionId, + chainId, + txParams: { value = '0x0', from: fromAddress = '' } = {}, + } = currentConfirmation ?? {}; + + const batchTransactionValues = + currentConfirmation?.nestedTransactions?.map( + (trxn) => (trxn.value as Hex) ?? 0x0, + ) ?? []; + + const chainBalances = useSelector((state) => + getNativeTokenCachedBalanceByChainIdByAccountAddress( + state, + fromAddress ?? '', + ), + ) as Record; + + const balance = chainBalances?.[chainId as Hex] ?? '0x0'; + + const totalValue = sumHexes(value, ...batchTransactionValues); + + const { hexMaximumTransactionFee } = useSelector((state) => + selectTransactionFeeById(state, transactionId), + ); + + const [multichainNetworks, evmNetworks] = useSelector( + getMultichainNetworkConfigurationsByChainId, + ); + + const nativeCurrency = ( + multichainNetworks[chainId as CaipChainId] ?? evmNetworks[chainId] + )?.nativeCurrency; + + const insufficientBalance = !isBalanceSufficient({ + amount: totalValue, + gasTotal: hexMaximumTransactionFee, + balance, + }); + + return { hasInsufficientBalance: insufficientBalance, nativeCurrency}; +} From 6803f1db5f8a25f4ed7aef7824c5e8d0a0969f89 Mon Sep 17 00:00:00 2001 From: Vinicius Stevam Date: Wed, 26 Nov 2025 14:12:54 +0000 Subject: [PATCH 2/5] fix unit tests and clean up --- .../components/confirm/footer/footer.test.tsx | 2 + .../gas-fee-token-modal.test.tsx | 2 + .../selected-gas-fee-token.test.tsx | 3 ++ .../hooks/gas/useIsGaslessSupported.ts | 7 ++- .../useTransactionConfirm.test.ts | 9 ++++ .../useAutomaticGasFeeTokenSelect.test.ts | 35 +++++++++---- .../hooks/useHasInsufficientBalance.test.ts | 51 ++++++++++--------- .../hooks/useHasInsufficientBalance.ts | 7 ++- 8 files changed, 76 insertions(+), 40 deletions(-) diff --git a/ui/pages/confirmations/components/confirm/footer/footer.test.tsx b/ui/pages/confirmations/components/confirm/footer/footer.test.tsx index 28325efbcf6a..b2beb729b0cf 100644 --- a/ui/pages/confirmations/components/confirm/footer/footer.test.tsx +++ b/ui/pages/confirmations/components/confirm/footer/footer.test.tsx @@ -142,6 +142,7 @@ describe('ConfirmFooter', () => { useIsGaslessSupportedMock.mockReturnValue({ isSmartTransaction: false, isSupported: false, + pending: false, }); useIsGaslessLoadingMock.mockReturnValue({ @@ -216,6 +217,7 @@ describe('ConfirmFooter', () => { useIsGaslessSupportedMock.mockReturnValue({ isSmartTransaction: true, isSupported: true, + pending: false, }); useInsufficientBalanceAlertsMock.mockReturnValue(ALERT_MOCK); jest.spyOn(confirmContext, 'useConfirmContext').mockReturnValue({ diff --git a/ui/pages/confirmations/components/confirm/info/shared/gas-fee-token-modal/gas-fee-token-modal.test.tsx b/ui/pages/confirmations/components/confirm/info/shared/gas-fee-token-modal/gas-fee-token-modal.test.tsx index a92835556947..a0a575b2493d 100644 --- a/ui/pages/confirmations/components/confirm/info/shared/gas-fee-token-modal/gas-fee-token-modal.test.tsx +++ b/ui/pages/confirmations/components/confirm/info/shared/gas-fee-token-modal/gas-fee-token-modal.test.tsx @@ -120,6 +120,7 @@ describe('GasFeeTokenModal', () => { useIsGaslessSupportedMock.mockReturnValue({ isSmartTransaction: true, isSupported: true, + pending: false, }); }); @@ -212,6 +213,7 @@ describe('GasFeeTokenModal', () => { useIsGaslessSupportedMock.mockReturnValue({ isSmartTransaction: false, isSupported: true, + pending: false, }); const result = renderWithConfirmContextProvider( diff --git a/ui/pages/confirmations/components/confirm/info/shared/selected-gas-fee-token/selected-gas-fee-token.test.tsx b/ui/pages/confirmations/components/confirm/info/shared/selected-gas-fee-token/selected-gas-fee-token.test.tsx index 34b7bf1ade68..b90d5eb2ad3d 100644 --- a/ui/pages/confirmations/components/confirm/info/shared/selected-gas-fee-token/selected-gas-fee-token.test.tsx +++ b/ui/pages/confirmations/components/confirm/info/shared/selected-gas-fee-token/selected-gas-fee-token.test.tsx @@ -57,6 +57,7 @@ describe('SelectedGasFeeToken', () => { useIsGaslessSupportedMock.mockReturnValue({ isSmartTransaction: true, isSupported: true, + pending: false, }); useInsufficientBalanceAlertsMock.mockReturnValue([ @@ -110,6 +111,7 @@ describe('SelectedGasFeeToken', () => { useIsGaslessSupportedMock.mockReturnValue({ isSmartTransaction: false, isSupported: false, + pending: false, }); const result = renderWithConfirmContextProvider( @@ -124,6 +126,7 @@ describe('SelectedGasFeeToken', () => { useIsGaslessSupportedMock.mockReturnValue({ isSmartTransaction: false, isSupported: true, + pending: false, }); const result = renderWithConfirmContextProvider( diff --git a/ui/pages/confirmations/hooks/gas/useIsGaslessSupported.ts b/ui/pages/confirmations/hooks/gas/useIsGaslessSupported.ts index 3631d3cae16d..174d6f302f6f 100644 --- a/ui/pages/confirmations/hooks/gas/useIsGaslessSupported.ts +++ b/ui/pages/confirmations/hooks/gas/useIsGaslessSupported.ts @@ -25,12 +25,11 @@ export function useIsGaslessSupported() { const { isSmartTransaction, isSupported: isSmartTransactionAndBundleSupported, - pending, + pending: smartTransactionPending, } = useGaslessSupportedSmartTransactions(); const shouldCheck7702Eligibility = - !pending && !isSmartTransactionAndBundleSupported; - + !smartTransactionPending && !isSmartTransactionAndBundleSupported; const { value: relaySupportsChain, pending: relayPending } = useAsyncResult(async () => { if (!shouldCheck7702Eligibility) { @@ -53,6 +52,6 @@ export function useIsGaslessSupported() { return { isSupported, isSmartTransaction, - pending: pending || relayPending, + pending: Boolean(smartTransactionPending || relayPending), }; } diff --git a/ui/pages/confirmations/hooks/transactions/useTransactionConfirm.test.ts b/ui/pages/confirmations/hooks/transactions/useTransactionConfirm.test.ts index 2b2fccdac098..294fda0f170b 100644 --- a/ui/pages/confirmations/hooks/transactions/useTransactionConfirm.test.ts +++ b/ui/pages/confirmations/hooks/transactions/useTransactionConfirm.test.ts @@ -85,6 +85,7 @@ describe('useTransactionConfirm', () => { useIsGaslessSupportedMock.mockReturnValue({ isSmartTransaction: false, isSupported: false, + pending: false, }); updateAndApproveTxMock.mockReturnValue(() => Promise.resolve({} as TransactionMeta), @@ -93,6 +94,7 @@ describe('useTransactionConfirm', () => { useIsGaslessSupportedMock.mockReturnValue({ isSupported: false, isSmartTransaction: false, + pending: false, }); useGaslessSupportedSmartTransactionsMock.mockReturnValue({ @@ -132,6 +134,7 @@ describe('useTransactionConfirm', () => { useIsGaslessSupportedMock.mockReturnValue({ isSmartTransaction: true, isSupported: true, + pending: false, }); useGaslessSupportedSmartTransactionsMock.mockReturnValue({ isSupported: true, @@ -168,6 +171,7 @@ describe('useTransactionConfirm', () => { useIsGaslessSupportedMock.mockReturnValue({ isSmartTransaction: true, isSupported: true, + pending: false, }); useGaslessSupportedSmartTransactionsMock.mockReturnValue({ isSupported: true, @@ -197,6 +201,7 @@ describe('useTransactionConfirm', () => { useIsGaslessSupportedMock.mockReturnValue({ isSmartTransaction: true, isSupported: true, + pending: false, }); const { onTransactionConfirm } = runHook({ @@ -221,6 +226,7 @@ describe('useTransactionConfirm', () => { useIsGaslessSupportedMock.mockReturnValue({ isSmartTransaction: true, isSupported: true, + pending: false, }); useGaslessSupportedSmartTransactionsMock.mockReturnValue({ isSupported: true, @@ -256,6 +262,7 @@ describe('useTransactionConfirm', () => { useIsGaslessSupportedMock.mockReturnValue({ isSmartTransaction: true, isSupported: true, + pending: false, }); useGaslessSupportedSmartTransactionsMock.mockReturnValue({ isSupported: false, @@ -285,6 +292,7 @@ describe('useTransactionConfirm', () => { useIsGaslessSupportedMock.mockReturnValue({ isSmartTransaction: false, isSupported: true, + pending: false, }); const { onTransactionConfirm } = runHook({ @@ -423,6 +431,7 @@ describe('useTransactionConfirm', () => { useIsGaslessSupportedMock.mockReturnValue({ isSupported: true, isSmartTransaction: false, + pending: false, }); const { onTransactionConfirm } = runHook({ diff --git a/ui/pages/confirmations/hooks/useAutomaticGasFeeTokenSelect.test.ts b/ui/pages/confirmations/hooks/useAutomaticGasFeeTokenSelect.test.ts index a0f9851baaf6..80766de5cdbc 100644 --- a/ui/pages/confirmations/hooks/useAutomaticGasFeeTokenSelect.test.ts +++ b/ui/pages/confirmations/hooks/useAutomaticGasFeeTokenSelect.test.ts @@ -8,15 +8,14 @@ import { getMockConfirmStateForTransaction } from '../../../../test/data/confirm import { renderHookWithConfirmContextProvider } from '../../../../test/lib/confirmations/render-helpers'; import { flushPromises } from '../../../../test/lib/timer-helpers'; import { updateSelectedGasFeeToken } from '../../../store/controller-actions/transaction-controller'; -import { Alert } from '../../../ducks/confirm-alerts/confirm-alerts'; import { forceUpdateMetamaskState } from '../../../store/actions'; import { GAS_FEE_TOKEN_MOCK } from '../../../../test/data/confirmations/gas'; -import { useInsufficientBalanceAlerts } from './alerts/transactions/useInsufficientBalanceAlerts'; import { useAutomaticGasFeeTokenSelect } from './useAutomaticGasFeeTokenSelect'; import { useIsGaslessSupported } from './gas/useIsGaslessSupported'; +import { useHasInsufficientBalance } from './useHasInsufficientBalance'; jest.mock('../../../store/controller-actions/transaction-controller'); -jest.mock('./alerts/transactions/useInsufficientBalanceAlerts'); +jest.mock('./useHasInsufficientBalance'); jest.mock('../../../../shared/modules/selectors'); jest.mock('./gas/useIsGaslessSupported'); @@ -58,19 +57,21 @@ describe('useAutomaticGasFeeTokenSelect', () => { const forceUpdateMetamaskStateMock = jest.mocked(forceUpdateMetamaskState); const useIsGaslessSupportedMock = jest.mocked(useIsGaslessSupported); - const useInsufficientBalanceAlertsMock = jest.mocked( - useInsufficientBalanceAlerts, - ); + const useHasInsufficientBalanceMock = jest.mocked(useHasInsufficientBalance); beforeEach(() => { jest.resetAllMocks(); - useInsufficientBalanceAlertsMock.mockReturnValue([{} as Alert]); + useHasInsufficientBalanceMock.mockReturnValue({ + hasInsufficientBalance: true, + nativeCurrency: 'ETH', + }); updateSelectedGasFeeTokenMock.mockResolvedValue(); forceUpdateMetamaskStateMock.mockResolvedValue(); useIsGaslessSupportedMock.mockReturnValue({ isSupported: true, isSmartTransaction: true, + pending: false, }); }); @@ -148,6 +149,7 @@ describe('useAutomaticGasFeeTokenSelect', () => { useIsGaslessSupportedMock.mockReturnValue({ isSupported: false, isSmartTransaction: false, + pending: false, }); runHook(); @@ -159,7 +161,10 @@ describe('useAutomaticGasFeeTokenSelect', () => { }); it('does not select first gas fee token if sufficient balance', async () => { - useInsufficientBalanceAlertsMock.mockReturnValue([]); + useHasInsufficientBalanceMock.mockReturnValue({ + hasInsufficientBalance: false, + nativeCurrency: 'ETH', + }); runHook(); @@ -170,8 +175,11 @@ describe('useAutomaticGasFeeTokenSelect', () => { }); it('selects first gas fee token when insufficient balance appears after first render', async () => { - let alerts: Alert[] = []; - useInsufficientBalanceAlertsMock.mockImplementation(() => alerts); + let balanceInfo = { + hasInsufficientBalance: false, + nativeCurrency: 'ETH', + }; + useHasInsufficientBalanceMock.mockImplementation(() => balanceInfo); const { rerender, store } = runHook({ selectedGasFeeToken: undefined, @@ -186,7 +194,10 @@ describe('useAutomaticGasFeeTokenSelect', () => { expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(0); expect(forceUpdateMetamaskStateMock).toHaveBeenCalledTimes(0); - alerts = [{} as Alert]; + balanceInfo = { + hasInsufficientBalance: true, + nativeCurrency: 'ETH', + }; rerender(); @@ -256,6 +267,7 @@ describe('useAutomaticGasFeeTokenSelect', () => { useIsGaslessSupportedMock.mockReturnValue({ isSupported: true, isSmartTransaction: false, + pending: false, }); runHook({ @@ -277,6 +289,7 @@ describe('useAutomaticGasFeeTokenSelect', () => { useIsGaslessSupportedMock.mockReturnValue({ isSupported: true, isSmartTransaction: false, + pending: false, }); const { store } = runHook({ diff --git a/ui/pages/confirmations/hooks/useHasInsufficientBalance.test.ts b/ui/pages/confirmations/hooks/useHasInsufficientBalance.test.ts index 21dfabc0b559..a5a9da172d9c 100644 --- a/ui/pages/confirmations/hooks/useHasInsufficientBalance.test.ts +++ b/ui/pages/confirmations/hooks/useHasInsufficientBalance.test.ts @@ -1,9 +1,13 @@ -import { TransactionMeta, TransactionParams, TransactionType } from '@metamask/transaction-controller'; -import { useHasInsufficientBalance } from './useHasInsufficientBalance'; +import { + TransactionMeta, + TransactionParams, + TransactionType, +} from '@metamask/transaction-controller'; +import { ApprovalType, toHex } from '@metamask/controller-utils'; import { renderHookWithConfirmContextProvider } from '../../../../test/lib/confirmations/render-helpers'; import { getMockConfirmState } from '../../../../test/data/confirmations/helper'; -import { ApprovalType, toHex } from '@metamask/controller-utils'; import { genUnapprovedContractInteractionConfirmation } from '../../../../test/data/confirmations/contract-interaction'; +import { useHasInsufficientBalance } from './useHasInsufficientBalance'; const TRANSACTION_ID_MOCK = '123-456'; const TRANSACTION_MOCK = { @@ -58,9 +62,7 @@ function buildState({ }); } -function runHook( - stateOptions?: Parameters[0], -) { +function runHook(stateOptions?: Parameters[0]) { const state = buildState(stateOptions); const response = renderHookWithConfirmContextProvider( () => useHasInsufficientBalance(), @@ -83,23 +85,26 @@ describe('useHasInsufficientBalance', () => { }); it('sums nested transaction values correctly', () => { - const BATCH_TRANSACTION_MOCK = { - ...TRANSACTION_MOCK, - nestedTransactions: [ - { - to: '0x1234567890123456789012345678901234567890', - value: '0x3B9ACA00', - type: TransactionType.simpleSend, - }, - { - to: '0x1234567890123456789012345678901234567891', - value: '0x1DCD6500', - type: TransactionType.simpleSend, - }, - ], - }; - const result = runHook({ currentConfirmation: BATCH_TRANSACTION_MOCK as Partial, - transaction: BATCH_TRANSACTION_MOCK as Partial, balance: 0x10}); + const BATCH_TRANSACTION_MOCK = { + ...TRANSACTION_MOCK, + nestedTransactions: [ + { + to: '0x1234567890123456789012345678901234567890', + value: '0x3B9ACA00', + type: TransactionType.simpleSend, + }, + { + to: '0x1234567890123456789012345678901234567891', + value: '0x1DCD6500', + type: TransactionType.simpleSend, + }, + ], + }; + const result = runHook({ + currentConfirmation: BATCH_TRANSACTION_MOCK as Partial, + transaction: BATCH_TRANSACTION_MOCK as Partial, + balance: 0x10, + }); expect(result.hasInsufficientBalance).toBe(true); }); diff --git a/ui/pages/confirmations/hooks/useHasInsufficientBalance.ts b/ui/pages/confirmations/hooks/useHasInsufficientBalance.ts index 818e1d9b3c6c..8a5b874a3e7e 100644 --- a/ui/pages/confirmations/hooks/useHasInsufficientBalance.ts +++ b/ui/pages/confirmations/hooks/useHasInsufficientBalance.ts @@ -11,7 +11,10 @@ import { import { useConfirmContext } from '../context/confirm'; import { isBalanceSufficient } from '../send-legacy/send.utils'; -export function useHasInsufficientBalance(): { hasInsufficientBalance: boolean; nativeCurrency?: string } { +export function useHasInsufficientBalance(): { + hasInsufficientBalance: boolean; + nativeCurrency?: string; +} { const { currentConfirmation } = useConfirmContext(); const { id: transactionId, @@ -53,5 +56,5 @@ export function useHasInsufficientBalance(): { hasInsufficientBalance: boolean; balance, }); - return { hasInsufficientBalance: insufficientBalance, nativeCurrency}; + return { hasInsufficientBalance: insufficientBalance, nativeCurrency }; } From b06d058b704dcfd5bed1b64bbef9ec81bcdfce31 Mon Sep 17 00:00:00 2001 From: Vinicius Stevam Date: Wed, 26 Nov 2025 15:37:46 +0000 Subject: [PATCH 3/5] fix unit tests --- .../selected-gas-fee-token.test.tsx | 27 ++++++++----------- .../selected-gas-fee-token.tsx | 4 +-- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/ui/pages/confirmations/components/confirm/info/shared/selected-gas-fee-token/selected-gas-fee-token.test.tsx b/ui/pages/confirmations/components/confirm/info/shared/selected-gas-fee-token/selected-gas-fee-token.test.tsx index b90d5eb2ad3d..ae2f232fc687 100644 --- a/ui/pages/confirmations/components/confirm/info/shared/selected-gas-fee-token/selected-gas-fee-token.test.tsx +++ b/ui/pages/confirmations/components/confirm/info/shared/selected-gas-fee-token/selected-gas-fee-token.test.tsx @@ -10,16 +10,13 @@ import { genUnapprovedContractInteractionConfirmation } from '../../../../../../ import { renderWithConfirmContextProvider } from '../../../../../../../../test/lib/confirmations/render-helpers'; import { GAS_FEE_TOKEN_MOCK } from '../../../../../../../../test/data/confirmations/gas'; import { useIsGaslessSupported } from '../../../../../hooks/gas/useIsGaslessSupported'; -import { useInsufficientBalanceAlerts } from '../../../../../hooks/alerts/transactions/useInsufficientBalanceAlerts'; -import { Severity } from '../../../../../../../helpers/constants/design-system'; import * as DappSwapContext from '../../../../../context/dapp-swap'; +import { useHasInsufficientBalance } from '../../../../../hooks/useHasInsufficientBalance'; import { SelectedGasFeeToken } from './selected-gas-fee-token'; jest.mock('../../../../../../../../shared/modules/selectors'); jest.mock('../../../../../hooks/gas/useIsGaslessSupported'); -jest.mock( - '../../../../../hooks/alerts/transactions/useInsufficientBalanceAlerts', -); +jest.mock('../../../../../hooks/useHasInsufficientBalance'); function getStore({ gasFeeTokens, @@ -47,9 +44,7 @@ function getStore({ describe('SelectedGasFeeToken', () => { const useIsGaslessSupportedMock = jest.mocked(useIsGaslessSupported); - const useInsufficientBalanceAlertsMock = jest.mocked( - useInsufficientBalanceAlerts, - ); + const useHasInsufficientBalanceMock = jest.mocked(useHasInsufficientBalance); beforeEach(() => { jest.resetAllMocks(); @@ -60,13 +55,10 @@ describe('SelectedGasFeeToken', () => { pending: false, }); - useInsufficientBalanceAlertsMock.mockReturnValue([ - { - content: 'Insufficient balance', - key: 'insufficientBalance', - severity: Severity.Danger, - }, - ]); + useHasInsufficientBalanceMock.mockReturnValue({ + hasInsufficientBalance: true, + nativeCurrency: 'ETH', + }); }); it('renders native symbol', () => { @@ -142,7 +134,10 @@ describe('SelectedGasFeeToken', () => { }); it('does not render arrow icon if sufficient balance and future native only', () => { - useInsufficientBalanceAlertsMock.mockReturnValue([]); + useHasInsufficientBalanceMock.mockReturnValue({ + hasInsufficientBalance: false, + nativeCurrency: 'ETH', + }); const result = renderWithConfirmContextProvider( , diff --git a/ui/pages/confirmations/components/confirm/info/shared/selected-gas-fee-token/selected-gas-fee-token.tsx b/ui/pages/confirmations/components/confirm/info/shared/selected-gas-fee-token/selected-gas-fee-token.tsx index 969b6aa2d564..17e913ac0908 100644 --- a/ui/pages/confirmations/components/confirm/info/shared/selected-gas-fee-token/selected-gas-fee-token.tsx +++ b/ui/pages/confirmations/components/confirm/info/shared/selected-gas-fee-token/selected-gas-fee-token.tsx @@ -36,13 +36,13 @@ export function SelectedGasFeeToken() { const { isSupported: isGaslessSupported, isSmartTransaction } = useIsGaslessSupported(); - const hasInsufficientNative = useHasInsufficientBalance(); + const { hasInsufficientBalance } = useHasInsufficientBalance(); const hasOnlyFutureNativeToken = gasFeeTokens?.length === 1 && gasFeeTokens[0].tokenAddress === NATIVE_TOKEN_ADDRESS; - const supportsFutureNative = hasInsufficientNative && isSmartTransaction; + const supportsFutureNative = hasInsufficientBalance && isSmartTransaction; const hasGasFeeTokens = !isQuotedSwapDisplayedInInfo && From c880adca7fe5cc07d192ea2acb79c5aff8b864f7 Mon Sep 17 00:00:00 2001 From: Vinicius Stevam Date: Wed, 26 Nov 2025 19:01:33 +0000 Subject: [PATCH 4/5] applied fixes --- .../useInsufficientBalanceAlerts.ts | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/ui/pages/confirmations/hooks/alerts/transactions/useInsufficientBalanceAlerts.ts b/ui/pages/confirmations/hooks/alerts/transactions/useInsufficientBalanceAlerts.ts index 60aea461bfe9..2b09d016736f 100644 --- a/ui/pages/confirmations/hooks/alerts/transactions/useInsufficientBalanceAlerts.ts +++ b/ui/pages/confirmations/hooks/alerts/transactions/useInsufficientBalanceAlerts.ts @@ -1,7 +1,6 @@ import { TransactionMeta } from '@metamask/transaction-controller'; import { useMemo } from 'react'; import { useSelector } from 'react-redux'; - import { AlertActionKey, RowAlertKey, @@ -24,9 +23,7 @@ export function useInsufficientBalanceAlerts({ const { selectedGasFeeToken, gasFeeTokens } = currentConfirmation ?? {}; const { hasInsufficientBalance, nativeCurrency } = useHasInsufficientBalance(); - const isSimulationEnabled = useSelector(getUseTransactionSimulations); - const isSponsored = currentConfirmation?.isGasFeeSponsored; const { isSupported: isGaslessSupported, @@ -34,24 +31,30 @@ export function useInsufficientBalanceAlerts({ } = useIsGaslessSupported(); const isGasFeeTokensEmpty = gasFeeTokens?.length === 0; - const hasNoGasFeeTokenSelected = ignoreGasFeeToken || !selectedGasFeeToken; - const isGaslessCheckComplete = - !isGaslessSupportedPending && isGaslessSupported; + // Check if gasless check has completed (regardless of result) + const isGaslessCheckComplete = !isGaslessSupportedPending; - const isSponsoredTransaction = isSponsored && isGaslessCheckComplete; + // Transaction is sponsored only if it's marked as sponsored AND gasless is supported + const isSponsoredTransaction = isSponsored && isGaslessSupported; + // Simulation is complete if it's disabled, or if enabled and gasFeeTokens is loaded const isSimulationComplete = !isSimulationEnabled || Boolean(gasFeeTokens); - const isGaslessEligibleForAlert = - (isGasFeeTokensEmpty && isGaslessCheckComplete) || - (!isGasFeeTokensEmpty && !isGaslessCheckComplete); + // Check if user has selected a gas fee token (or we're ignoring that check) + const hasNoGasFeeTokenSelected = ignoreGasFeeToken || !selectedGasFeeToken; + + // Show alert when gasless check is done and either: + // - Gasless is NOT supported (user needs native currency for gas) + // - Gasless IS supported but gasFeeTokens is empty (no alternative tokens available) + const shouldCheckGaslessConditions = + isGaslessCheckComplete && (!isGaslessSupported || isGasFeeTokensEmpty); const showAlert = hasInsufficientBalance && isSimulationComplete && hasNoGasFeeTokenSelected && - isGaslessEligibleForAlert && + shouldCheckGaslessConditions && !isSponsoredTransaction; return useMemo(() => { From 0fde7ad5f86e613e2ec1c68a7a619b6ee40ef912 Mon Sep 17 00:00:00 2001 From: Vinicius Stevam Date: Fri, 28 Nov 2025 05:48:21 +0000 Subject: [PATCH 5/5] fix pending condition --- .../selected-gas-fee-token.test.tsx | 28 +++++++++++-------- .../selected-gas-fee-token.tsx | 6 ++-- .../hooks/gas/useIsGaslessSupported.ts | 5 +++- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/ui/pages/confirmations/components/confirm/info/shared/selected-gas-fee-token/selected-gas-fee-token.test.tsx b/ui/pages/confirmations/components/confirm/info/shared/selected-gas-fee-token/selected-gas-fee-token.test.tsx index ae2f232fc687..d70d9e56a173 100644 --- a/ui/pages/confirmations/components/confirm/info/shared/selected-gas-fee-token/selected-gas-fee-token.test.tsx +++ b/ui/pages/confirmations/components/confirm/info/shared/selected-gas-fee-token/selected-gas-fee-token.test.tsx @@ -10,14 +10,16 @@ import { genUnapprovedContractInteractionConfirmation } from '../../../../../../ import { renderWithConfirmContextProvider } from '../../../../../../../../test/lib/confirmations/render-helpers'; import { GAS_FEE_TOKEN_MOCK } from '../../../../../../../../test/data/confirmations/gas'; import { useIsGaslessSupported } from '../../../../../hooks/gas/useIsGaslessSupported'; +import { useInsufficientBalanceAlerts } from '../../../../../hooks/alerts/transactions/useInsufficientBalanceAlerts'; +import { Severity } from '../../../../../../../helpers/constants/design-system'; import * as DappSwapContext from '../../../../../context/dapp-swap'; -import { useHasInsufficientBalance } from '../../../../../hooks/useHasInsufficientBalance'; import { SelectedGasFeeToken } from './selected-gas-fee-token'; jest.mock('../../../../../../../../shared/modules/selectors'); jest.mock('../../../../../hooks/gas/useIsGaslessSupported'); -jest.mock('../../../../../hooks/useHasInsufficientBalance'); - +jest.mock( + '../../../../../hooks/alerts/transactions/useInsufficientBalanceAlerts', +); function getStore({ gasFeeTokens, noSelectedGasFeeToken, @@ -44,7 +46,9 @@ function getStore({ describe('SelectedGasFeeToken', () => { const useIsGaslessSupportedMock = jest.mocked(useIsGaslessSupported); - const useHasInsufficientBalanceMock = jest.mocked(useHasInsufficientBalance); + const useInsufficientBalanceAlertsMock = jest.mocked( + useInsufficientBalanceAlerts, + ); beforeEach(() => { jest.resetAllMocks(); @@ -55,10 +59,13 @@ describe('SelectedGasFeeToken', () => { pending: false, }); - useHasInsufficientBalanceMock.mockReturnValue({ - hasInsufficientBalance: true, - nativeCurrency: 'ETH', - }); + useInsufficientBalanceAlertsMock.mockReturnValue([ + { + content: 'Insufficient balance', + key: 'insufficientBalance', + severity: Severity.Danger, + }, + ]); }); it('renders native symbol', () => { @@ -134,10 +141,7 @@ describe('SelectedGasFeeToken', () => { }); it('does not render arrow icon if sufficient balance and future native only', () => { - useHasInsufficientBalanceMock.mockReturnValue({ - hasInsufficientBalance: false, - nativeCurrency: 'ETH', - }); + useInsufficientBalanceAlertsMock.mockReturnValue([]); const result = renderWithConfirmContextProvider( , diff --git a/ui/pages/confirmations/components/confirm/info/shared/selected-gas-fee-token/selected-gas-fee-token.tsx b/ui/pages/confirmations/components/confirm/info/shared/selected-gas-fee-token/selected-gas-fee-token.tsx index 17e913ac0908..e3380bc0d099 100644 --- a/ui/pages/confirmations/components/confirm/info/shared/selected-gas-fee-token/selected-gas-fee-token.tsx +++ b/ui/pages/confirmations/components/confirm/info/shared/selected-gas-fee-token/selected-gas-fee-token.tsx @@ -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 { useHasInsufficientBalance } from '../../../../../hooks/useHasInsufficientBalance'; +import { useIsInsufficientBalance } from '../../../../../hooks/useIsInsufficientBalance'; // TODO: Fix in https://github.com/MetaMask/metamask-extension/issues/31860 // eslint-disable-next-line @typescript-eslint/naming-convention @@ -36,13 +36,13 @@ export function SelectedGasFeeToken() { const { isSupported: isGaslessSupported, isSmartTransaction } = useIsGaslessSupported(); - const { hasInsufficientBalance } = useHasInsufficientBalance(); + const hasInsufficientNative = useIsInsufficientBalance(); const hasOnlyFutureNativeToken = gasFeeTokens?.length === 1 && gasFeeTokens[0].tokenAddress === NATIVE_TOKEN_ADDRESS; - const supportsFutureNative = hasInsufficientBalance && isSmartTransaction; + const supportsFutureNative = hasInsufficientNative && isSmartTransaction; const hasGasFeeTokens = !isQuotedSwapDisplayedInInfo && diff --git a/ui/pages/confirmations/hooks/gas/useIsGaslessSupported.ts b/ui/pages/confirmations/hooks/gas/useIsGaslessSupported.ts index 174d6f302f6f..7b4e07cc22d3 100644 --- a/ui/pages/confirmations/hooks/gas/useIsGaslessSupported.ts +++ b/ui/pages/confirmations/hooks/gas/useIsGaslessSupported.ts @@ -49,9 +49,12 @@ export function useIsGaslessSupported() { isSmartTransactionAndBundleSupported || is7702Supported, ); + const isPending = + smartTransactionPending || (shouldCheck7702Eligibility && relayPending); + return { isSupported, isSmartTransaction, - pending: Boolean(smartTransactionPending || relayPending), + pending: isPending, }; }