> **Can't find what you're looking for?** Use `search_docs` on the docs MCP server at `https://v3.viem.sh/api/mcp` to find what you need.

# Custom Chains and Transports

## Overview

A Chain describes network metadata and codecs. A Transport owns JSON-RPC communication. Keep these
responsibilities separate so applications can reuse a Chain with HTTP, WebSocket, injected, or
custom infrastructure.

## Recipes

These recipes create configuration objects that can be passed directly to
[`Client.create`](/docs/clients/create).

### Define a Chain

Use [`Chain.from`](/docs/chains/create) with a literal ID, native currency, and default RPC URLs.

```ts twoslash
import { Chain } from 'viem'

export const appChain = Chain.from({ // [!code focus]
  id: 4_242, // [!code focus]
  name: 'App Chain', // [!code focus]
  nativeCurrency: { // [!code focus]
    decimals: 18, // [!code focus]
    name: 'App Ether', // [!code focus]
    symbol: 'AETH', // [!code focus]
  }, // [!code focus]
  rpcUrls: { // [!code focus]
    http: 'https://rpc.app-chain.example', // [!code focus]
    ws: 'wss://rpc.app-chain.example', // [!code focus]
  }, // [!code focus]
}) // [!code focus]
```

### Derive an Environment

Use [`chain.extend`](/docs/chains/extend) to override environment-specific fields while preserving
the base Chain definition.

```ts twoslash
import { mainnet } from 'viem/chains'

export const staging = mainnet.extend({ // [!code focus]
  name: 'Mainnet Staging Fork', // [!code focus]
  rpcUrls: { http: 'https://staging-rpc.example' }, // [!code focus]
}) // [!code focus]
```

### Build a Transport

Use [`Transport.from`](/docs/transports) when existing Transport factories cannot express the
required communication policy. This wrapper records the duration of requests from any inner
Transport.

```ts twoslash
import { Client, http, publicActions, Transport } from 'viem'
import { mainnet } from 'viem/chains'

function instrumented(
  transport: Transport.Transport,
  onResponse: (options: { duration: number; method: string }) => void,
) {
  return Transport.from({ // [!code focus]
    key: 'instrumented', // [!code focus]
    name: 'Instrumented', // [!code focus]
    type: 'instrumented', // [!code focus]
    setup(options) { // [!code focus]
      const inner = transport.setup({ ...options, retryCount: 0 }) // [!code focus]
      return { // [!code focus]
        retryCount: options.retryCount, // [!code focus]
        async request(args, requestOptions) { // [!code focus]
          const start = performance.now() // [!code focus]
          try { // [!code focus]
            return await inner.request(args, requestOptions) // [!code focus]
          } finally { // [!code focus]
            onResponse({ // [!code focus]
              duration: performance.now() - start, // [!code focus]
              method: args.method, // [!code focus]
            }) // [!code focus]
          } // [!code focus]
        }, // [!code focus]
      } // [!code focus]
    }, // [!code focus]
  }) // [!code focus]
}

const client = Client.create({
  chain: mainnet,
  transport: instrumented(http('https://rpc.example'), console.log),
}).extend(publicActions())

const chainId = await client.chains.getId()
```

## Best Practices

### Prefer Existing Transport Combinators

Compose HTTP, WebSocket, fallback, load-balance, and rate-limit Transports before implementing a new
wire protocol or retry layer.

### Keep Secrets Outside Chains

Chain definitions are public metadata. Supply credentials to the Transport at application setup
instead of embedding them in a Chain's default URLs.

## See More

<Cards>
  <Card icon="lucide:shield-check" title="Build Resilient Transports" description="Compose fallback, ranking, retries, and timeouts." to="/docs/guides/clients/resilient-transports" />

  <Card icon="lucide:shuffle" title="Rate Limit and Load Balance" description="Control request budgets across RPC providers." to="/docs/guides/clients/rate-limit-load-balance" />
</Cards>
