Guide
Calls & phases
Every EIP-8130 transaction carries a list of calls grouped into ordered phases. A phase is an atomic batch; completed phases persist even if a later one reverts. This is what powers deploy-on-first-use, sponsor-then-act, and multi-step flows.
The phased model
Calls are grouped into ordered phases. Each phase is an atomic batch: if any call in a phase reverts, that phase's state changes are discarded and every later phase is skipped. Crucially, phases that already completed persist, and the transaction is still included (nonce consumed, fee paid).

transaction.calls is an array of atomic batches. Once a batch completes successfully, it is committed and will not be reverted by a later failure.The calls shape
calls is an array of phases, each an array of AaCall:
import { type AaCalls, parseEther } from 'viem/experimental/eip8130'
const calls: AaCalls = [ // phase 0: runs first, atomically [{ to: tokenA, data: approveData }], // phase 1: runs only if phase 0 succeeded [ { to: router, data: swapData }, { to: recipient, value: parseEther('0.01') }, ],]Each AaCall is { to, data?, value? }:
to: target address.data: calldata (defaults to0x).value: wei to send (defaults to0n). This is an ERC-5792-style intent: it is realized by the account's wallet bytecode and never travels on the EIP-8130 wire.
Flat vs. phased
sendCalls8130 accepts either shape. A flat array is sugar for a single phase; pass a nested array to control phases explicitly. estimateGas8130 and prepareTransaction8130 always take the phased form.
import { sendCalls8130 } from 'viem/experimental/eip8130'
// One atomic phase (flat array):await sendCalls8130(client, { account, calls: [{ to: a, data }, { to: b, data }], gas: 300_000n,})
// Two phases (nested array):await sendCalls8130(client, { account, calls: [[{ to: a, data }], [{ to: b, data }]], gas: 300_000n,})Value-bearing calls
A phase with no value passes each call straight to the wire as [to, data]. As soon as a phase contains any value-bearing call, the whole phase is collapsed into a single wallet-routed call: by default a self-call to the account's executeBatch(Call[]), which performs each CALL while preserving the phase's atomicity. encodeWalletCalls implements this normalization (and sendCalls8130 applies it for you).
import { encodeWalletCalls, parseEther } from 'viem/experimental/eip8130'
const wire = encodeWalletCalls({ account: account.address, calls: [[{ to: recipient, value: parseEther('1'), data: '0x' }]],})// -> [[{ to: account.address, data: executeBatch([...]) }]]Custom executors
Wallets whose bytecode does not expose executeBatch supply their own encodeExecute: a function mapping a phase's normalized calls to a single wallet call.
import { type EncodeExecute, sendCalls8130 } from 'viem/experimental/eip8130'import { encodeFunctionData } from 'viem'
const encodeExecute: EncodeExecute = ({ account, calls }) => ({ to: account, data: encodeFunctionData({ abi: myWalletAbi, functionName: 'execute', args: [calls], }),})
await sendCalls8130(client, { account, calls, gas: 300_000n, encodeExecute })Choosing phases
- Single atomic batch: one phase. Everything succeeds together or nothing does (e.g. approve + swap that must not half-apply).
- Ordered, independently-committing steps: multiple phases. Use for pay-then-act, a sponsored phase-0 token transfer followed by the user's calls, or any flow where an early step must persist even if a later one reverts.