Guide
Multichain
An EIP-8130 account has one deterministic address on every chain. Where the chain supports EIP-8130 natively you transact directly; elsewhere the same address is driven through ERC-4337. Actor changes can be signed once and replayed everywhere.
Same address everywhere
The address is a deterministic function of the wallet code, salt, and initial actors, so it is identical across chains. Native EIP-8130 is live on Vibenet (chain id 84538453). Tell Viem which chains run EIP-8130 natively with register8130Chains, and spread eip8130ChainConfig into the chain so core client.sendTransaction submits native AA transactions (type 0x79); elsewhere the same address falls back to ERC-4337.
import { defineChain } from 'viem'import { eip8130ChainConfig, is8130Enabled, register8130Chains,} from 'viem/eip8130'
export const vibenet = defineChain({ ...eip8130ChainConfig, id: 84_538_453, name: 'Vibenet Devnet', nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, rpcUrls: { default: { http: ['https://rpc.vibes.base.org'] } },})
// Mark chains where EIP-8130 is native (Vibenet today).register8130Chains(vibenet.id)is8130Enabled(client.chain.id) // → true on a native chain
// With eip8130ChainConfig spread in, core client.sendTransaction submits a// native AA transaction (type 0x79) for an EIP-8130 account.const hash = await client.sendTransaction({ account, calls: [{ to: recipient, data }],})Portable to non-8130 chains
On a chain without native EIP-8130, wrap the account as a Viem ERC-4337 Smart Account with toSmartAccount. Same address, same owner, driven through a bundler and EntryPoint. The full setup (initial actors, factory data) lives in Creating accounts.
import { toSmartAccount } from 'viem/eip8130'
// On a non-8130 chain, drive the SAME address via ERC-4337 (bundler + EntryPoint).const smartAccount = await toSmartAccount({ client, // pointed at the non-8130 chain owner, // the controlling signer address, // the account's canonical address})Replayable actor changes
A config change signed against chainId 0 is valid on any chain, so you can rotate keys once and land the change everywhere. It is ordered by a separate multichain sequence (distinct from the per-chain local one).
import { actorScope, authorizeActor, getConfigSequence, key,} from 'viem/eip8130'
// The multichain channel has its own sequence, distinct from the per-chain local one.const { multichain: sequence } = await getConfigSequence(client, { account: account.address,})
// The 'multichain' channel binds chainId 0 => the signed change is valid on ANY chain.const portableChange = await account.change( [authorizeActor(key.p256({ x, y }), { scope: actorScope.operator })], { channel: 'multichain', sequence },)Land it by including portableChange in a transaction's accountChanges on an 8130 chain, or apply it as a plain EVM call on any chain with encodeApplySignedAccountChangesData:
import { encodeApplySignedAccountChangesData } from 'viem/eip8130'
// Apply the same signed change on any chain as a plain EVM call.const data = encodeApplySignedAccountChangesData({ account: account.address, channel: portableChange.channel, // 'multichain' sequence: portableChange.sequence, changes: portableChange.changes, signature: portableChange.signature,})