Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/silver-otters-dive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'viem': minor
---

Added `filterChains` utility for filtering chain definitions by structured criteria.
8 changes: 4 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 45 additions & 0 deletions site/data/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ export const tokenLookupData = [
id: 10,
name: 'OP Mainnet',
},
{
address: '0x3D7F2C478aAfdB65542BCB44bCeeC05849999d2D',
id: 25,
name: 'Cronos Mainnet',
},
{
address: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
id: 50,
name: 'XDC Network',
},
{
address: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
id: 130,
Expand All @@ -39,21 +49,46 @@ export const tokenLookupData = [
id: 137,
name: 'Polygon',
},
{
address: '0x754704Bc059F8C67012fEd69BC8A327a5aafb603',
id: 143,
name: 'Monad',
},
{
address: '0x29219dd400f2Bf60E5a23d13Be72B486D4038894',
id: 146,
name: 'Sonic',
},
{
address: '0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4',
id: 324,
name: 'ZKsync Era',
},
{
address: '0x79A02482A880bCE3F13e09Da970dC34db4CD24d1',
id: 480,
name: 'World Chain',
},
{
address: '0xb88339CB7199b77E23DB6E890353E22632Ba630f',
id: 999,
name: 'HyperEVM',
},
{
address: '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392',
id: 1329,
name: 'Sei Network',
},
{
address: '0xa00C59fF5a080D2b954d0c75e46E22a0c371235a',
id: 1776,
name: 'Injective',
},
{
address: '0xCfb1186F4e93D60E60a8bDd997427D1F33bc372B',
id: 2818,
name: 'Morph',
},
{
address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
id: 8453,
Expand All @@ -64,6 +99,11 @@ export const tokenLookupData = [
id: 42161,
name: 'Arbitrum One',
},
{
address: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
id: 42220,
name: 'Celo',
},
{
address: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',
id: 43114,
Expand All @@ -84,6 +124,11 @@ export const tokenLookupData = [
id: 81224,
name: 'Codex',
},
{
address: '0x222365EF19F7947e5484218551B56bb3965Aa7aF',
id: 98866,
name: 'Plume',
},
],
currency: 'USD',
decimals: 6,
Expand Down
2 changes: 1 addition & 1 deletion site/pages/docs/utilities/extractChain.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,4 @@ The set of chains where the chain will be extracted from.

- **Type:** `number`

The ID of the chain to extract.
The ID of the chain to extract.
141 changes: 141 additions & 0 deletions site/pages/docs/utilities/filterChains.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
---
description: Filters chain definitions by structured criteria.
---

# filterChains

Filters chain definitions by structured criteria, such as token support or testnet status.

:::warning
This utility is experimental and may change in a future release.
:::

## Import

```ts
import { filterChains } from 'viem/utils'
```

## Usage

```ts
import * as chains from 'viem/chains'
import { usdc } from 'viem/tokens'
import { filterChains } from 'viem/utils'

const supportedChains = filterChains({
chains,
token: usdc,
testnet: true,
sort: 'name',
})

supportedChains[0]?.id
// ^? (property) id: 300 | 338 | 998 | 1301 | ...
```

The `token` criterion narrows the returned chain IDs to chains where the token has a configured address. This keeps token symbols type-safe when you pass a filtered chain into a client.

```ts
import { createClient, http, publicActions } from 'viem'
import * as chains from 'viem/chains'
import { usdc } from 'viem/tokens'
import { filterChains } from 'viem/utils'

const [chain] = filterChains({
chains,
token: usdc,
testnet: true,
})

if (chain) {
const client = createClient({
chain,
tokens: [usdc],
transport: http(),
}).extend(publicActions)

await client.token.getBalance({ token: 'usdc' })
}
```

:::warning
Importing all chains from `viem/chains` significantly increases bundle size. This pattern is recommended for scripts, server-side code, or other environments where bundle size is not a concern.
:::

## Returns

- **Type:** `Chain[]` (inferred)

The filtered chains.

When `token` is provided, each returned chain's `id` is narrowed to the token's supported chain IDs.

When `testnet: true` is provided, each returned chain's `testnet` property is narrowed to `true`.

## Parameters

### chains

- **Type:** `Record<string, unknown> | readonly unknown[]`

A chain registry or array. You can pass `import * as chains from 'viem/chains'` or an array of chain definitions.

```ts
import { mainnet, optimism, sepolia } from 'viem/chains'
import { filterChains } from 'viem/utils'

const testnets = filterChains({
chains: [mainnet, optimism, sepolia],
testnet: true,
})
```

### token

- **Type:** `Token`

Only include chains with an address for the token.

```ts
import * as chains from 'viem/chains'
import { usdc } from 'viem/tokens'
import { filterChains } from 'viem/utils'

const usdcChains = filterChains({
chains,
token: usdc,
})
```

### testnet

- **Type:** `boolean`

Only include testnets when `true`, or mainnets when `false`.

```ts
import * as chains from 'viem/chains'
import { filterChains } from 'viem/utils'

const testnets = filterChains({
chains,
testnet: true,
})
```

### sort

- **Type:** `'id' | 'name'`

Sort the matching chains by chain ID or name.

```ts
import * as chains from 'viem/chains'
import { filterChains } from 'viem/utils'

const sortedChains = filterChains({
chains,
sort: 'name',
})
```
4 changes: 4 additions & 0 deletions site/vocs.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1186,6 +1186,10 @@ export default defineConfig({
text: 'extractChain',
link: '/docs/utilities/extractChain',
},
{
text: 'filterChains',
link: '/docs/utilities/filterChains',
},
],
},
{
Expand Down
85 changes: 85 additions & 0 deletions src/utils/chain/filterChains.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { describe, expectTypeOf, test } from 'vitest'
import { privateKeyToAccount } from '../../accounts/privateKeyToAccount.js'
import { getBalance } from '../../actions/token/getBalance.js'
import * as chains from '../../chains/index.js'
import { createClient } from '../../clients/createClient.js'
import { publicActions } from '../../clients/decorators/public.js'
import { walletActions } from '../../clients/decorators/wallet.js'
import { http } from '../../clients/transports/http.js'
import { usdc, usdce } from '../../tokens/index.js'
import type { Chain } from '../../types/chain.js'
import { filterChains } from './filterChains.js'

const account = privateKeyToAccount(`0x${'1'.repeat(64)}`)

describe('filterChains', () => {
test('narrows chain id by token support', () => {
const supportedChains = filterChains({ chains, token: usdc })
const chain = supportedChains[0]

expectTypeOf(chain).toMatchTypeOf<
Chain & { id: keyof typeof usdc.addresses }
>()

if (chain) {
const client = createClient({
account,
chain,
tokens: [usdc],
transport: http(),
})
getBalance(client, { token: 'usdc' })
}
})

test('narrows decorated client token actions by token support', () => {
const supportedChains = filterChains({
chains,
sort: 'name',
testnet: true,
token: usdc,
})
const chain = supportedChains[0]

expectTypeOf(chain).toMatchTypeOf<
Chain & { id: keyof typeof usdc.addresses; testnet: true }
>()

if (chain) {
const client = createClient({
account,
chain,
tokens: [usdc],
transport: http(),
})
.extend(publicActions)
.extend(walletActions)

client.token.getBalance({ token: 'usdc' })
client.token.transfer({
amount: { formatted: '1' },
to: '0x',
token: 'usdc',
})
// @ts-expect-error USDC.e is not declared on the client.
client.token.getBalance({ token: 'usdc.e' })
}
})

test('does not narrow token symbols without token support criteria', () => {
const supportedChains = filterChains({ chains, testnet: true })
const chain = supportedChains[0]

if (chain) {
const client = createClient({
account,
chain,
tokens: [usdc, usdce],
transport: http(),
})

// @ts-expect-error chain id is not known to support USDC.
getBalance(client, { token: 'usdc' })
}
})
})
Loading
Loading