Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/hooks/useTokenLists.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
10 changes: 7 additions & 3 deletions src/hooks/useTokenLists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,14 @@ function combineTokenLists(results: Array<UseSuspenseQueryResult<TokenList>>): 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(),
Expand Down Expand Up @@ -201,6 +203,8 @@ export async function fetchTokenList(url: string): Promise<TokenList> {
}
}

const supportedChainIds = new Set<number>(Object.values(chains).map((c) => c.id))

/**
* Builds a native token object based on the chain ID.
*
Expand Down
Loading