diff --git a/app/components/Views/confirmations/components/confirmation-asset-polling-provider/confirmation-asset-polling-provider.test.tsx b/app/components/Views/confirmations/components/confirmation-asset-polling-provider/confirmation-asset-polling-provider.test.tsx index 7dd989deb650..030cacef7948 100644 --- a/app/components/Views/confirmations/components/confirmation-asset-polling-provider/confirmation-asset-polling-provider.test.tsx +++ b/app/components/Views/confirmations/components/confirmation-asset-polling-provider/confirmation-asset-polling-provider.test.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { Hex } from '@metamask/utils'; import { Text } from 'react-native'; import { TransactionMeta } from '@metamask/transaction-controller'; import renderWithProvider from '../../../../../util/test/renderWithProvider'; @@ -204,7 +205,7 @@ describe('ConfirmationAssetPollingProvider', () => { it('handles different chainId formats correctly', () => { const customTransactionMetadata = { ...mockTransactionMetadata, - chainId: '0x89' as `0x${string}`, + chainId: '0x89' as Hex, }; jest @@ -220,6 +221,96 @@ describe('ConfirmationAssetPollingProvider', () => { { state: {} }, ); + // The transaction's chainId should be included even if it's not in bridge chains + expect(mockAssetPollingProvider).toHaveBeenCalledWith( + { + chainIds: [CHAIN_ID_MOCK, CHAIN_ID_2_MOCK, '0x89'], + address: '0x935e73edb9ff52e23bac7f7e043a1ecd06d05477', + }, + expect.anything(), + ); + }); + + it('does not duplicate chainId when transaction chainId is already in bridge chains', () => { + const customTransactionMetadata = { + ...mockTransactionMetadata, + chainId: CHAIN_ID_MOCK as Hex, + }; + + jest + .spyOn(TransactionMetadataRequestHook, 'useTransactionMetadataRequest') + .mockReturnValue(customTransactionMetadata); + + const TestChild = () => Test Child; + + renderWithProvider( + + + , + { state: {} }, + ); + + // Should not duplicate CHAIN_ID_MOCK since it's already in bridge chains + expect(mockAssetPollingProvider).toHaveBeenCalledWith( + { + chainIds: [CHAIN_ID_MOCK, CHAIN_ID_2_MOCK], + address: '0x935e73edb9ff52e23bac7f7e043a1ecd06d05477', + }, + expect.anything(), + ); + }); + + it('handles empty bridge chains list', () => { + selectEnabledSourceChainsMock.mockReturnValue([]); + + const customTransactionMetadata = { + ...mockTransactionMetadata, + chainId: '0x89' as Hex, + }; + + jest + .spyOn(TransactionMetadataRequestHook, 'useTransactionMetadataRequest') + .mockReturnValue(customTransactionMetadata); + + const TestChild = () => Test Child; + + renderWithProvider( + + + , + { state: {} }, + ); + + // Should still include transaction chainId even with no bridge chains + expect(mockAssetPollingProvider).toHaveBeenCalledWith( + { + chainIds: ['0x89'], + address: '0x935e73edb9ff52e23bac7f7e043a1ecd06d05477', + }, + expect.anything(), + ); + }); + + it('filters out non-EVM chains from bridge chains', () => { + const customTransactionMetadata = { + ...mockTransactionMetadata, + chainId: CHAIN_ID_MOCK as Hex, + }; + + jest + .spyOn(TransactionMetadataRequestHook, 'useTransactionMetadataRequest') + .mockReturnValue(customTransactionMetadata); + + const TestChild = () => Test Child; + + renderWithProvider( + + + , + { state: {} }, + ); + + // Should only include EVM chains (0x1, 0x2), not 'SolanaChainId' expect(mockAssetPollingProvider).toHaveBeenCalledWith( { chainIds: [CHAIN_ID_MOCK, CHAIN_ID_2_MOCK], @@ -228,5 +319,36 @@ describe('ConfirmationAssetPollingProvider', () => { expect.anything(), ); }); + + it('handles newly added network from dapp scenario', () => { + // Simulate a newly added custom network that is not in bridge chains + const newCustomChainId = '0xa4b1'; // Arbitrum One + const customTransactionMetadata = { + ...mockTransactionMetadata, + chainId: newCustomChainId as Hex, + }; + + jest + .spyOn(TransactionMetadataRequestHook, 'useTransactionMetadataRequest') + .mockReturnValue(customTransactionMetadata); + + const TestChild = () => Test Child; + + renderWithProvider( + + + , + { state: {} }, + ); + + // The newly added network should be included for polling + expect(mockAssetPollingProvider).toHaveBeenCalledWith( + { + chainIds: [CHAIN_ID_MOCK, CHAIN_ID_2_MOCK, newCustomChainId], + address: '0x935e73edb9ff52e23bac7f7e043a1ecd06d05477', + }, + expect.anything(), + ); + }); }); }); diff --git a/app/components/Views/confirmations/components/confirmation-asset-polling-provider/confirmation-asset-polling-provider.tsx b/app/components/Views/confirmations/components/confirmation-asset-polling-provider/confirmation-asset-polling-provider.tsx index dc1e01e2a096..3ef96566b759 100644 --- a/app/components/Views/confirmations/components/confirmation-asset-polling-provider/confirmation-asset-polling-provider.tsx +++ b/app/components/Views/confirmations/components/confirmation-asset-polling-provider/confirmation-asset-polling-provider.tsx @@ -23,11 +23,21 @@ export const ConfirmationAssetPollingProvider = ({ const transactionMeta = useTransactionMetadataRequest(); const bridgeChains = useSelector(selectEnabledSourceChains); - const pollChainIds = useMemo( - () => - bridgeChains.filter((chain) => chain.isEvm).map((chain) => chain.chainId), - [bridgeChains], - ) as Hex[]; + const pollChainIds = useMemo(() => { + const bridgeChainIds = bridgeChains + .filter((chain) => chain.isEvm) + .map((chain) => chain.chainId as Hex); + + // Always include the transaction's chain to ensure balance is available + // This is critical when a user adds a new network from a dapp and immediately + // attempts to transact - the new network may not be in the bridge chains list + const txChainId = transactionMeta?.chainId as Hex | undefined; + if (txChainId && !bridgeChainIds.includes(txChainId)) { + return [...bridgeChainIds, txChainId]; + } + + return bridgeChainIds; + }, [bridgeChains, transactionMeta?.chainId]); if (!transactionMeta) { return children;