diff --git a/ui/hooks/bridge/__snapshots__/useTokensWithFiltering.test.ts.snap b/ui/hooks/bridge/__snapshots__/useTokensWithFiltering.test.ts.snap index 8fceeffe9730..88ab10baba61 100644 --- a/ui/hooks/bridge/__snapshots__/useTokensWithFiltering.test.ts.snap +++ b/ui/hooks/bridge/__snapshots__/useTokensWithFiltering.test.ts.snap @@ -1,7 +1,18 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`useTokensWithFiltering should not return tokens that are not in the allowlist 1`] = ` +exports[`useTokensWithFiltering should fetch bridge tokens if cached tokens have old timestamp 1`] = ` [ + { + "address": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", + "balance": "0.00184", + "chainId": "0x1", + "decimals": 6, + "image": "images/contract/uni.svg", + "isNative": false, + "string": "0.00184", + "tokenFiatAmount": 0.004232, + "type": "TOKEN", + }, { "address": "0x0000000000000000000000000000000000000000", "balance": "0.000000000000000014", @@ -24,6 +35,46 @@ exports[`useTokensWithFiltering should not return tokens that are not in the all "tokenFiatAmount": 2.5242500000000003e-14, "type": "NATIVE", }, + { + "address": "0x514910771af9ca656af840dff83e8264ecf986ca", + "balance": "1", + "chainId": "0x1", + "image": "images/contract/chainlink.svg", + "isNative": false, + "string": "1", + "tokenFiatAmount": null, + "type": "TOKEN", + }, + { + "address": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", + "balance": "0", + "chainId": "0xe708", + "image": undefined, + "isNative": false, + "string": "0", + "tokenFiatAmount": null, + "type": "TOKEN", + }, + { + "address": "0x0000000000000000000000000000000000000000", + "balance": "0", + "chainId": "0x1", + "decimals": 18, + "iconUrl": "./images/eth_logo.svg", + "image": "./images/eth_logo.svg", + "name": "Ether", + "string": "0", + "symbol": "ETH", + "type": "NATIVE", + }, + { + "address": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", + "balance": "", + "chainId": "0x1", + "decimals": 6, + "string": undefined, + "type": "TOKEN", + }, { "address": "0x6b3595068778dd592e39a122f4f5a5cf09c90fe2", "aggregators": [], @@ -39,18 +90,6 @@ exports[`useTokensWithFiltering should not return tokens that are not in the all "symbol": "SUSHI", "type": "TOKEN", }, - { - "address": "0x0000000000000000000000000000000000000000", - "balance": "0x0", - "chainId": "0x1", - "decimals": 18, - "iconUrl": "./images/eth_logo.svg", - "image": "./images/eth_logo.svg", - "name": "Ether", - "string": "0x0", - "symbol": "ETH", - "type": "NATIVE", - }, { "address": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", "aggregators": [], @@ -65,17 +104,31 @@ exports[`useTokensWithFiltering should not return tokens that are not in the all "symbol": "UNI", "type": "TOKEN", }, + { + "address": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "aggregators": [], + "balance": "", + "chainId": "0x1", + "decimals": 6, + "erc20": true, + "iconUrl": "images/contract/usdt.svg", + "image": "images/contract/usdt.svg", + "name": "Tether USD", + "string": undefined, + "symbol": "USDT", + "type": "TOKEN", + }, ] `; -exports[`useTokensWithFiltering should return all tokens when chainId === activeChainId, sorted by balance 1`] = ` +exports[`useTokensWithFiltering should return all tokens when chainId !== activeChainId and chainId has been imported, sorted by balance 1`] = ` [ { "address": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", "balance": "0.00184", "chainId": "0x1", "decimals": 6, - "image": undefined, + "image": "images/contract/uni.svg", "isNative": false, "string": "0.00184", "tokenFiatAmount": 0.004232, @@ -107,12 +160,34 @@ exports[`useTokensWithFiltering should return all tokens when chainId === active "address": "0x514910771af9ca656af840dff83e8264ecf986ca", "balance": "1", "chainId": "0x1", - "image": undefined, + "image": "images/contract/chainlink.svg", "isNative": false, "string": "1", "tokenFiatAmount": null, "type": "TOKEN", }, + { + "address": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", + "balance": "0", + "chainId": "0xe708", + "image": undefined, + "isNative": false, + "string": "0", + "tokenFiatAmount": null, + "type": "TOKEN", + }, + { + "address": "0x0000000000000000000000000000000000000000", + "balance": "0", + "chainId": "0x1", + "decimals": 18, + "iconUrl": "./images/eth_logo.svg", + "image": "./images/eth_logo.svg", + "name": "Ether", + "string": "0", + "symbol": "ETH", + "type": "NATIVE", + }, { "address": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", "balance": "", @@ -137,22 +212,70 @@ exports[`useTokensWithFiltering should return all tokens when chainId === active "type": "TOKEN", }, { - "address": "0x0000000000000000000000000000000000000000", - "balance": "0x0", + "address": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", + "aggregators": [], + "balance": "", "chainId": "0x1", "decimals": 18, - "iconUrl": "./images/eth_logo.svg", - "image": "./images/eth_logo.svg", - "name": "Ether", - "string": "0x0", - "symbol": "ETH", + "erc20": true, + "iconUrl": "images/contract/uni.svg", + "image": "images/contract/uni.svg", + "name": "Uniswap", + "string": undefined, + "symbol": "UNI", + "type": "TOKEN", + }, + { + "address": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "aggregators": [], + "balance": "", + "chainId": "0x1", + "decimals": 6, + "erc20": true, + "iconUrl": "images/contract/usdt.svg", + "image": "images/contract/usdt.svg", + "name": "Tether USD", + "string": undefined, + "symbol": "USDT", + "type": "TOKEN", + }, +] +`; + +exports[`useTokensWithFiltering should return all tokens when chainId !== activeChainId and chainId has not been imported, sorted by balance 1`] = ` +[ + { + "address": "0x0000000000000000000000000000000000000000", + "balance": "0", + "chainId": "0x89", + "decimals": 18, + "iconUrl": "./images/pol-token.svg", + "image": "./images/pol-token.svg", + "name": "Polygon", + "string": "0", + "symbol": "POL", "type": "NATIVE", }, + { + "address": "0x6b3595068778dd592e39a122f4f5a5cf09c90fe2", + "aggregators": [], + "balance": "", + "chainId": "0x89", + "decimals": 18, + "erc20": true, + "erc721": false, + "iconUrl": "images/contract/sushi.svg", + "image": "images/contract/sushi.svg", + "name": "SushiSwap", + "string": undefined, + "symbol": "SUSHI", + "type": "TOKEN", + }, { "address": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", "aggregators": [], "balance": "", - "chainId": "0x1", + "chainId": "0x89", "decimals": 18, "erc20": true, "iconUrl": "images/contract/uni.svg", @@ -166,7 +289,7 @@ exports[`useTokensWithFiltering should return all tokens when chainId === active "address": "0xdac17f958d2ee523a2206206994597c13d831ec7", "aggregators": [], "balance": "", - "chainId": "0x1", + "chainId": "0x89", "decimals": 6, "erc20": true, "iconUrl": "images/contract/usdt.svg", @@ -178,15 +301,85 @@ exports[`useTokensWithFiltering should return all tokens when chainId === active }, { "address": "0x0000000000000000000000000000000000000000", - "balance": "0x0", - "chainId": "0x1", + "balance": "0", + "chainId": "0x89", "decimals": 18, "iconUrl": "./images/eth_logo.svg", - "image": "./images/eth_logo.svg", + "image": "./images/pol-token.svg", "name": "Ether", - "string": "0x0", + "string": "0", "symbol": "ETH", "type": "NATIVE", }, + { + "address": "0x12652c6d93fdb6f4f37d48a8687783c782bb0d10", + "aggregators": [], + "balance": "", + "chainId": "0x89", + "decimals": 18, + "erc20": true, + "iconUrl": "images/contract/NGL.svg", + "image": "images/contract/NGL.svg", + "name": "Entangle", + "string": undefined, + "symbol": "NGL", + "type": "TOKEN", + }, + { + "address": "0xb50721bcf8d664c30412cfbc6cf7a15145234ad1", + "aggregators": [], + "balance": "", + "chainId": "0x89", + "decimals": 18, + "erc20": true, + "iconUrl": "images/contract/Arb.svg", + "image": "images/contract/Arb.svg", + "name": "Arbitrum", + "string": undefined, + "symbol": "ARB", + "type": "TOKEN", + }, + { + "address": "0x4d0528598f916fd1d8dc80e5f54a8feedcfd4b18", + "aggregators": [], + "balance": "", + "chainId": "0x89", + "decimals": 18, + "erc20": true, + "iconUrl": "images/contract/ATOS.svg", + "image": "images/contract/ATOS.svg", + "name": "Atoshi", + "string": undefined, + "symbol": "ATOS", + "type": "TOKEN", + }, + { + "address": "0x57b946008913b82e4df85f501cbaed910e58d26c", + "aggregators": [], + "balance": "", + "chainId": "0x89", + "decimals": 18, + "erc20": true, + "iconUrl": "images/contract/POND.svg", + "image": "images/contract/POND.svg", + "name": "POND", + "string": undefined, + "symbol": "POND", + "type": "TOKEN", + }, + { + "address": "0x5eed99d066a8caf10f3e4327c1b3d8b673485eed", + "aggregators": [], + "balance": "", + "chainId": "0x89", + "decimals": 18, + "erc20": true, + "iconUrl": "images/contract/SEED.svg", + "image": "images/contract/SEED.svg", + "name": "SEED", + "string": undefined, + "symbol": "SEED", + "type": "TOKEN", + }, ] `; diff --git a/ui/hooks/bridge/useTokensWithFiltering.test.ts b/ui/hooks/bridge/useTokensWithFiltering.test.ts index e6903756bcfd..d9d137cd1fce 100644 --- a/ui/hooks/bridge/useTokensWithFiltering.test.ts +++ b/ui/hooks/bridge/useTokensWithFiltering.test.ts @@ -3,23 +3,34 @@ import { createBridgeMockStore } from '../../../test/jest/mock-store'; import { STATIC_MAINNET_TOKEN_LIST } from '../../../shared/constants/tokens'; import { SWAPS_CHAINID_DEFAULT_TOKEN_MAP } from '../../../shared/constants/swaps'; import { CHAIN_IDS } from '../../../shared/constants/network'; +import { MINUTE } from '../../../shared/constants/time'; import { useTokensWithFiltering } from './useTokensWithFiltering'; -const mockUseTokenTracker = jest - .fn() - .mockReturnValue({ tokensWithBalances: [] }); -jest.mock('../useTokenTracker', () => ({ - useTokenTracker: () => mockUseTokenTracker(), +const NATIVE_TOKEN = SWAPS_CHAINID_DEFAULT_TOKEN_MAP[CHAIN_IDS.MAINNET]; + +const mockFetchBridgeTokens = jest.fn().mockResolvedValue({ + [NATIVE_TOKEN.address]: NATIVE_TOKEN, + ...STATIC_MAINNET_TOKEN_LIST, +}); +jest.mock('../../../shared/modules/bridge-utils/bridge.util', () => ({ + fetchBridgeTokens: (c: string) => mockFetchBridgeTokens(c), })); -const NATIVE_TOKEN = SWAPS_CHAINID_DEFAULT_TOKEN_MAP[CHAIN_IDS.MAINNET]; +const mockFetchTopAssetsList = jest.fn().mockResolvedValue([ + { address: '0x6b3595068778dd592e39a122f4f5a5cf09c90fe2' }, // UNI + { address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984' }, // USDC + { address: '0xdac17f958d2ee523a2206206994597c13d831ec7' }, // USDT +]); +jest.mock('../../pages/swaps/swaps.util', () => ({ + fetchTopAssetsList: (c: string) => mockFetchTopAssetsList(c), +})); describe('useTokensWithFiltering', () => { - afterEach(() => { + beforeEach(() => { jest.clearAllMocks(); }); - it('should return all tokens when chainId === activeChainId, sorted by balance', () => { + it('should return all tokens when chainId !== activeChainId and chainId has been imported, sorted by balance', async () => { const mockStore = createBridgeMockStore({ metamaskStateOverrides: { completedOnboarding: true, @@ -33,36 +44,80 @@ describe('useTokensWithFiltering', () => { ], }, }, + tokensChainsCache: { + [CHAIN_IDS.MAINNET]: { + timestamp: Date.now(), + data: { + [NATIVE_TOKEN.address]: NATIVE_TOKEN, + ...STATIC_MAINNET_TOKEN_LIST, + }, + }, + }, }, }); - const { result } = renderHookWithProvider( - () => - useTokensWithFiltering( - { - [NATIVE_TOKEN.address]: NATIVE_TOKEN, - ...STATIC_MAINNET_TOKEN_LIST, + + const { result, waitForNextUpdate } = renderHookWithProvider(() => { + const { filteredTokenListGenerator } = useTokensWithFiltering( + CHAIN_IDS.MAINNET, + ); + return filteredTokenListGenerator; + }, mockStore); + + await waitForNextUpdate(); + + expect(mockFetchTopAssetsList).toHaveBeenCalledTimes(1); + expect(mockFetchTopAssetsList).toHaveBeenCalledWith('0x1'); + expect(mockFetchBridgeTokens).not.toHaveBeenCalled(); + // The first 10 tokens returned + const first10Tokens = [...result.current(() => true)].slice(0, 10); + expect(first10Tokens).toMatchSnapshot(); + }); + + it('should fetch bridge tokens if cached tokens have old timestamp', async () => { + const mockStore = createBridgeMockStore({ + metamaskStateOverrides: { + completedOnboarding: true, + allDetectedTokens: { + '0x1': { + '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': [ + { + address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', + decimals: 6, + }, // USDC + ], }, - [ - { address: '0x6b3595068778dd592e39a122f4f5a5cf09c90fe2' }, // UNI - { address: NATIVE_TOKEN.address }, - { address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984' }, // USDC - { address: '0xdac17f958d2ee523a2206206994597c13d831ec7' }, // USDT - ], - { - [CHAIN_IDS.MAINNET]: new Set( - Object.keys(STATIC_MAINNET_TOKEN_LIST), - ), + }, + tokensChainsCache: { + [CHAIN_IDS.MAINNET]: { + timestamp: Date.now() - 11 * MINUTE, + data: { + [NATIVE_TOKEN.address]: NATIVE_TOKEN, + ...STATIC_MAINNET_TOKEN_LIST, + }, }, - CHAIN_IDS.MAINNET, - ), - mockStore, - ); + }, + }, + }); + + const { result, waitForNextUpdate } = renderHookWithProvider(() => { + const { filteredTokenListGenerator } = useTokensWithFiltering( + CHAIN_IDS.MAINNET, + ); + return filteredTokenListGenerator; + }, mockStore); + + await waitForNextUpdate(); + + expect(mockFetchTopAssetsList).toHaveBeenCalledTimes(1); + expect(mockFetchTopAssetsList).toHaveBeenCalledWith('0x1'); + expect(mockFetchBridgeTokens).toHaveBeenCalledTimes(1); + expect(mockFetchBridgeTokens).toHaveBeenCalledWith('0x1'); // The first 10 tokens returned const first10Tokens = [...result.current(() => true)].slice(0, 10); expect(first10Tokens).toMatchSnapshot(); }); - it('should not return tokens that are not in the allowlist', () => { + it('should return all tokens when chainId !== activeChainId and chainId has not been imported, sorted by balance', async () => { const mockStore = createBridgeMockStore({ metamaskStateOverrides: { completedOnboarding: true, @@ -76,33 +131,27 @@ describe('useTokensWithFiltering', () => { ], }, }, + tokensChainsCache: {}, }, }); - const { result } = renderHookWithProvider( - () => - useTokensWithFiltering( - { - [NATIVE_TOKEN.address]: NATIVE_TOKEN, - ...STATIC_MAINNET_TOKEN_LIST, - }, - [ - { address: '0x6b3595068778dd592e39a122f4f5a5cf09c90fe2' }, // UNI - { address: NATIVE_TOKEN.address }, - { address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984' }, // USDC - { address: '0xdac17f958d2ee523a2206206994597c13d831ec7' }, // USDT - ], - // Only 1 token in allowlist - { - [CHAIN_IDS.MAINNET]: new Set([ - '0x6b3595068778dd592e39a122f4f5a5cf09c90fe2', - ]), - }, - CHAIN_IDS.MAINNET, - ), - mockStore, - ); - // The first 5 tokens returned - const first5Tokens = [...result.current(() => true)].slice(0, 5); - expect(first5Tokens).toMatchSnapshot(); + + const { result, waitForNextUpdate } = renderHookWithProvider(() => { + const { filteredTokenListGenerator } = useTokensWithFiltering( + CHAIN_IDS.POLYGON, + ); + return filteredTokenListGenerator; + }, mockStore); + await waitForNextUpdate(); + + expect(mockFetchTopAssetsList).toHaveBeenCalledTimes(1); + expect(mockFetchTopAssetsList).toHaveBeenCalledWith('0x89'); + expect(mockFetchBridgeTokens).toHaveBeenCalledTimes(1); + expect(mockFetchBridgeTokens).toHaveBeenCalledWith('0x89'); + // The first 10 tokens returned + const first10Tokens = [ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ...result.current((_s: any, _a: any, c: string) => c === '0x89'), + ].slice(0, 10); + expect(first10Tokens).toMatchSnapshot(); }); });