diff --git a/src/hooks/useTokenLists.test.ts b/src/hooks/useTokenLists.test.ts index 6763fcaf..93ad81f0 100644 --- a/src/hooks/useTokenLists.test.ts +++ b/src/hooks/useTokenLists.test.ts @@ -222,6 +222,29 @@ describe('useTokenLists', () => { expect(nativeToken?.symbol).toBe('ETH') }) + it('filters out tokens whose chainId is not present in viem/chains and does not log', () => { + // biome-ignore lint/suspicious/noExplicitAny: mocking internal combine param + vi.mocked(tanstackQuery.useSuspenseQueries).mockImplementation(({ combine }: any) => { + const ropstenToken: Token = { + address: '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6', + chainId: 3, + decimals: 18, + name: 'Wrapped Ether (Ropsten)', + symbol: 'WETH', + } + return combine([mockSuspenseQueryResult([mockToken1, ropstenToken])]) + }) + + const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) + const { result } = renderHook(() => useTokenLists(), { wrapper }) + + expect(result.current.tokens.some((t) => t.chainId === 3)).toBe(false) + expect(result.current.tokensByChainId[3]).toBeUndefined() + expect(errorSpy).not.toHaveBeenCalled() + + errorSpy.mockRestore() + }) + it('filters out tokens that fail schema validation', () => { // biome-ignore lint/suspicious/noExplicitAny: mocking internal combine param vi.mocked(tanstackQuery.useSuspenseQueries).mockImplementation(({ combine }: any) => { diff --git a/src/hooks/useTokenLists.ts b/src/hooks/useTokenLists.ts index 8f53e944..7a9370df 100644 --- a/src/hooks/useTokenLists.ts +++ b/src/hooks/useTokenLists.ts @@ -110,12 +110,14 @@ function combineTokenLists(results: Array>): T .flatMap((result) => result.data.tokens) // tokenSchema enforces EVM address format (0x + 40 hex chars), so non-EVM entries // (e.g. Solana tokens from @uniswap/default-token-list v18+) are silently dropped here. + // Tokens whose chainId is not present in viem/chains (e.g. deprecated testnets like + // Ropsten/Rinkeby still shipped by @uniswap/default-token-list) are also dropped so + // buildNativeToken never throws and tokensByChainId stays free of unreachable buckets. // Supporting non-EVM chains would require changes to the address schema, chain config, // wallet integration, and contract lookup -- out of scope for this EVM-focused starter kit. .filter((token) => { - const result = tokenSchema.safeParse(token) - - return result.success + if (!tokenSchema.safeParse(token).success) return false + return supportedChainIds.has(token.chainId) }) .map((token) => [tokenKey(token), token]), ).values(), @@ -201,6 +203,8 @@ export async function fetchTokenList(url: string): Promise { } } +const supportedChainIds = new Set(Object.values(chains).map((c) => c.id)) + /** * Builds a native token object based on the chain ID. *