Build your own Client
You can build your own viem Client by using the createClient
function and optionally extending (.extend
) it – this is how viem's internal Clients (Public, Wallet, and Test) are built.
Building your own Client is useful if you have specific requirements for how the Client should behave, and if you want to extend that Client with custom functionality (ie. create an EIP-4337 Bundler Client, or geth Debug Client).
The createClient
function sets up a base viem Client with a given Transport configured with a Chain. After that, you can extend the Client with custom properties (that could be Actions or other configuration).
Import
import { createClient } from 'viem'
import { createClient } from 'viem'
Usage
Initialize a Client with your desired Chain (e.g. mainnet
) and Transport (e.g. http
).
import { createClient, http } from 'viem'
import { mainnet } from 'viem/chains'
const client = createClient({
chain: mainnet,
transport: http()
})
import { createClient, http } from 'viem'
import { mainnet } from 'viem/chains'
const client = createClient({
chain: mainnet,
transport: http()
})
Next, you can either extend your Client with Actions or configuration, or you can use it as-is for the purpose of maximizing tree-shaking in your app.
Extending with Actions or configuration
You can extend your Client with custom Actions or configuration by using the .extend
function.
Below is a naive implementation of implementing a geth Debug Client with a traceCall
Action that uses the debug_traceCall
RPC method.
import {
createClient,
http,
formatTransactionRequest,
type CallParameters
} from 'viem'
import { mainnet } from 'viem/chains'
const debugClient = createClient({
chain: mainnet,
transport: http(),
}).extend(client => ({
// ...
async traceCall(args: CallParameters) {
return client.request({
method: 'debug_traceCall',
params: [formatTransactionRequest(args), 'latest', {}]
})
},
// ...
}))
const response = await debugClient.traceCall({
account: '0xdeadbeef29292929192939494959594933929292',
to: '0xde929f939d939d393f939393f93939f393929023',
gas: 69420n,
data: '0xf00d4b5d00000000000000000000000001291230982139282304923482304912923823920000000000000000000000001293123098123928310239129839291010293810'
})
// { failed: false, gas: 69420, returnValue: '...', structLogs: [] }
import {
createClient,
http,
formatTransactionRequest,
type CallParameters
} from 'viem'
import { mainnet } from 'viem/chains'
const debugClient = createClient({
chain: mainnet,
transport: http(),
}).extend(client => ({
// ...
async traceCall(args: CallParameters) {
return client.request({
method: 'debug_traceCall',
params: [formatTransactionRequest(args), 'latest', {}]
})
},
// ...
}))
const response = await debugClient.traceCall({
account: '0xdeadbeef29292929192939494959594933929292',
to: '0xde929f939d939d393f939393f93939f393929023',
gas: 69420n,
data: '0xf00d4b5d00000000000000000000000001291230982139282304923482304912923823920000000000000000000000001293123098123928310239129839291010293810'
})
// { failed: false, gas: 69420, returnValue: '...', structLogs: [] }
For a more succinct implementation of using .extend
, check out viem's Public Client implementation extended with Public Actions.
Tree-shaking
You can use the Client as-is, with no decorated Actions, to maximize tree-shaking in your app. This is useful if you are pedantic about bundle size and want to only include the Actions you use.
In the example below, instead of calling getBlock
from the Public Client, we are importing the Action directly from viem
and then injecting our Client as the first parameter to the Action.
import { createClient, http } from 'viem'
import { mainnet } from 'viem/chains'
import { getBlock, sendTransaction } from 'viem/actions'
const client = createClient({
chain: mainnet,
transport: http()
})
const blockNumber = await getBlock(client, { blockTag: 'latest' })
const hash = await sendTransaction(client, { ... })
import { createClient, http } from 'viem'
import { mainnet } from 'viem/chains'
import { getBlock, sendTransaction } from 'viem/actions'
const client = createClient({
chain: mainnet,
transport: http()
})
const blockNumber = await getBlock(client, { blockTag: 'latest' })
const hash = await sendTransaction(client, { ... })
Parameters
transport
- Type: Transport
The Transport of the Public Client.
const client = createClient({
chain: mainnet,
transport: http(),
})
const client = createClient({
chain: mainnet,
transport: http(),
})
account (optional)
- Type:
Account | Address
The Account to use for the Client. This will be used for Actions that require an account
as an argument.
Accepts a JSON-RPC Account or Local Account (Private Key, etc).
import { privateKeyToAccount } from 'viem/accounts'
const client = createClient({
account: privateKeyToAccount('0x...')
chain: mainnet,
transport: http(),
})
import { privateKeyToAccount } from 'viem/accounts'
const client = createClient({
account: privateKeyToAccount('0x...')
chain: mainnet,
transport: http(),
})
chain (optional)
- Type: Chain
The Chain of the Public Client.
const client = createClient({
chain: mainnet,
transport: http(),
})
const client = createClient({
chain: mainnet,
transport: http(),
})
batch (optional)
Flags for batch settings.
batch.multicall (optional)
- Type:
boolean | MulticallBatchOptions
- Default:
false
Toggle to enable eth_call
multicall aggregation.
const client = createClient({
batch: {
multicall: true,
},
chain: mainnet,
transport: http(),
})
const client = createClient({
batch: {
multicall: true,
},
chain: mainnet,
transport: http(),
})
batch.multicall.batchSize (optional)
- Type:
number
- Default:
1_024
The maximum size (in bytes) for each multicall (aggregate3
) calldata chunk.
Note: Some RPC Providers limit the amount of calldata that can be sent in a single request. It is best to check with your RPC Provider to see if there are any calldata size limits to
eth_call
requests.
const client = createClient({
batch: {
multicall: {
batchSize: 512,
},
},
chain: mainnet,
transport: http(),
})
const client = createClient({
batch: {
multicall: {
batchSize: 512,
},
},
chain: mainnet,
transport: http(),
})
batch.multicall.wait (optional)
- Type:
number
- Default:
0
(zero delay)
The maximum number of milliseconds to wait before sending a batch.
const client = createClient({
batch: {
multicall: {
wait: 16,
},
},
chain: mainnet,
transport: http(),
})
const client = createClient({
batch: {
multicall: {
wait: 16,
},
},
chain: mainnet,
transport: http(),
})
key (optional)
- Type:
string
- Default:
"public"
A key for the Client.
const client = createClient({
chain: mainnet,
key: 'public',
transport: http(),
})
const client = createClient({
chain: mainnet,
key: 'public',
transport: http(),
})
name (optional)
- Type:
string
- Default:
"Public Client"
A name for the Client.
const client = createClient({
chain: mainnet,
name: 'Public Client',
transport: http(),
})
const client = createClient({
chain: mainnet,
name: 'Public Client',
transport: http(),
})
pollingInterval (optional)
- Type:
number
- Default:
4_000
Frequency (in ms) for polling enabled Actions.
const client = createClient({
chain: mainnet,
pollingInterval: 10_000,
transport: http(),
})
const client = createClient({
chain: mainnet,
pollingInterval: 10_000,
transport: http(),
})