Guide
Sending transactions
An EIP-8130 transaction is an AA_TX_TYPE (0x79) envelope. sendTransaction fills nonce and fees, encodes calls, signs sender_auth (and payer_auth when sponsored), serializes, and submits via eth_sendRawTransaction. Use sendTransactionSync for a receipt in one round-trip.
Estimate gas
Unlike legacy EVM txs, the gas budget for an 8130 transaction is node-computed and required up front. estimateGas prices authentication from the shape of the auth blob (never a real signature), so you can estimate before signing. Pass a senderAuthAuthenticator hint (a canonical authenticator address) matching your signer.
import { parseEther } from 'viem'import { canonicalAuthenticators, estimateGas,} from 'viem/eip8130'
const gas = await estimateGas(client, { sender: account.address, // Include createChange only on the first (deploying) transaction. accountChanges: [account.createChange], // Each inner array is one atomic executeBatch phase. calls: [[{ to: recipient, value: parseEther('0.001') }]], senderAuthAuthenticator: canonicalAuthenticators.k1, // .p256 / .passkey})For a policy-gated actor (a session key), also pass senderActorId so the node simulates against the intended acting actor and resolves the right policy, instead of the account's self-actor.
Deploy on first use
Include account.createChange in accountChanges to deploy and run the first calls in one transaction.
import { parseEther } from 'viem'import { canonicalAuthenticators, estimateGas, sendTransaction, sendTransactionSync, waitForTransactionReceipt,} from 'viem/eip8130'
const calls = [{ to: recipient, value: parseEther('0.001') }]
const gas = await estimateGas(client, { sender: account.address, accountChanges: [account.createChange], calls: [calls], senderAuthAuthenticator: canonicalAuthenticators.k1,})
const hash = await sendTransaction(client, { account, accountChanges: [account.createChange], // omit on later txs calls, gas: (gas * 120n) / 100n,})
const receipt = await waitForTransactionReceipt(client, { hash })receipt.status // call phases only — not account changesreceipt.eip8130.phaseStatuses // e.g. ['0x1'] per call phase
// Or, in one round-trip (eth_sendRawTransactionSync): returns the receipt.const receiptSync = await sendTransactionSync(client, { account, accountChanges: [account.createChange], calls, gas: (gas * 120n) / 100n,})Reading receipts
Account changes and call phases have independent success. Do not treat receipt.status as "the whole transaction worked."
- Account changes apply at phase −1, as a validity gate. If the transaction mined, those changes applied. If they were invalid (stale sequence, wrong
actorId, revoking the signing key), the transaction is rejected at validation and never mines. There is no silent no-op. receipt.statusandallPhasesSucceededcover call phases only. A0x0receipt can still mean the account was created, delegated, or rotated.- Confirm actor changes from
ActorAuthorized/ActorRevokedlogs, or a state read (isActor,getActorConfig, a bumpedgetConfigSequence). Poll about one block if state lags the receipt. phaseStatusesis per call phase (0x1/0x0). The first0x0is the phase that reverted; later phases are skipped and also reported0x0.
Follow-up transactions
Once deployed, drop account changes and pass calls only. The 2D nonce sequence is read automatically.
const gas = await estimateGas(client, { sender: account.address, calls: [[{ to: token, data: transferData }]],})
const hash = await sendTransaction(client, { account, calls: [{ to: token, data: transferData }], gas: (gas * 120n) / 100n,})Native Core Integration
The examples above use the standalone sendTransaction(client, …) action. When your chain spreads eip8130ChainConfig (see Getting started), you can send the same AA transaction through core client.sendTransaction: the chain config routes an EIP-8130 account to a native 0x79 submit, and core client.getTransactionReceipt / waitForTransactionReceipt return the .eip8130 fields.
// With eip8130ChainConfig spread into the chain, core client.sendTransaction// submits a native AA tx (type 0x79) — gas is auto-estimated when omitted.const hash = await client.sendTransaction({ account, // newSmartAccount(...) / toAccount(...) calls: [{ to: recipient, value: parseEther('0.001') }], accountChanges: [account.createChange], // first tx only})
const receipt = await client.getTransactionReceipt({ hash })receipt.eip8130.phaseStatuses // ['0x1']// Note: sponsored (payer) sends are NOT available on core sendTransaction.Transaction expiry tracking
Any EIP-8130 transaction with a non-zero expiry (sequenced or nonce-free) can no longer land once the chain's latest block timestamp passes it. Thread the signed validBefore out of sendTransaction with onTransaction, then pass it to waitForTransactionReceipt. The wait fails fast with TransactionExpiredError instead of spinning until timeout.
import { sendTransaction, TransactionExpiredError, waitForTransactionReceipt,} from 'viem/eip8130'
// Thread the signed validBefore into the wait — reliable for any non-zero// expiry (sequenced or nonce-free), including auto-computed nonceless windows.let validBefore: bigint | undefinedconst hash = await sendTransaction(client, { account, calls, gas, onTransaction: (tx) => { validBefore = tx.validBefore },})
try { const receipt = await waitForTransactionReceipt(client, { hash, validBefore }) receipt.eip8130.phaseStatuses} catch (e) { if (e instanceof TransactionExpiredError) { // Tx can never land — resubmit with a fresh expiry. } else { throw e }}Batching calls
A flat calls array runs as one atomic phase. Nested arrays control phases explicitly. The full model is in Calls & phases.
const hash = await sendTransaction(client, { account, calls: [ { to: tokenA, data: approveData }, { to: router, data: swapData }, ], gas: 400_000n,})Sponsored (payer) gas
A payer account co-signs and pays gas: a key you hold or an ERC-8168 service. Token payment (e.g. USDV) typically uses an early call phase; details in Gas & payers.
const hash = await sendTransaction(client, { account, calls: [{ to: recipient, data }], gas: 250_000n, payer: { account: sponsor },})Lower-level control
prepareTransactionRequestfills chain id, nonce sequence, and EIP-1559 fees.account.signTransactionproduces auth blobs and a serialized envelope forsendRawTransaction.
Metadata
Transactions may carry an opaque, signed metadata field for attribution, memos, or off-chain commitments. It is covered by the same sender signature and never affects execution. See Metadata & attribution for the field and the experimental encoding.
- Full API: Viem. Sending a Transaction