Guide

Nonces & throughput

An EIP-8130 nonce is 2D: a channel key plus a sequence. Sequenced channels give independent ordered lanes for parallelism; the reserved nonce-free channel drops sequencing entirely and leans on expiry. Viem's nonce helper produces the right fields for each.

Three strategies

The nonce helper returns { nonceKey, nonceSequence?, validBefore? } that you spread straight into sendTransaction or prepareTransactionRequest.

nonce helperts
import { nonce, sendTransaction } from 'viem/eip8130'
// Standard single-file ordering (channel 0):
await sendTransaction(client, { account, calls, gas, ...nonce.sequential() })
// A specific parallel channel (its own counter):
await sendTransaction(client, { account, calls, gas, ...nonce.channel(1n) })
// A fresh random channel per send (fire-and-forget):
await sendTransaction(client, { account, calls, gas, ...nonce.randomChannel() })
// Nonce-free: no counter, replay-protected by expiry alone:
await sendTransaction(client, { account, calls, gas, ...nonce.nonceless({ expiresIn: 600 }) })

Parallel channels

Each 2D channel maintains its own sequential counter, so transactions in different channels are independent and may be mined out of order relative to one another. Use a fixed channel(key) for a stable lane, or randomChannel() for collision-free fire-and-forget sends. The next sequence is read from the node when you do not supply one.

Independent lanests
// Two independent channels can be mined in either order.
await sendTransaction(client, { account, calls: a, gas, ...nonce.channel(1n) })
await sendTransaction(client, { account, calls: b, gas, ...nonce.channel(2n) })
// Each channel's next sequence is read from the node automatically.

Nonce-free (expiring) mode

The reserved nonceKeyMax channel reads and increments no counter, so a transaction is not ordered against any other; replay protection comes solely from validBefore, a Unix timestamp in milliseconds evaluated against block.timestamp * 1000. It is ideal for fully parallel, retry-safe sends. The deadline must fall within the chain's nonceFreeMaxExpiryWindow; viem derives it from Date.now() by default (override via expiresIn/validBefore). The mechanics and the fee-invariant replay_id are covered in 2D nonces & nonceless.

nonce.noncelessts
import { nonce, sendTransaction } from 'viem/eip8130'
// Not ordered against anything; valid until a near-future expiry.
const hash = await sendTransaction(client, {
account,
calls,
gas: 250_000n,
...nonce.nonceless({ expiresIn: 600 }),
})

Reading a sequence manually

sendTransaction reads the next sequence for you. When you need it directly (e.g. to build and batch several sends yourself), read the per-channel counter with getTransactionCount (nonce-free channels have no counter and return an error).

getTransactionCountts
import { getTransactionCount } from 'viem/eip8130'
const sequence = await getTransactionCount(client, {
address: account.address,
nonceKey: 1n, // per-channel counter
})