Guide

Sub-accounts

EIP-8130 has no dedicated sub-account type. You compose two primitives: derive many accounts from one owner (distinct salts), and link them with delegate actors so a primary account can drive its sub-accounts, without sharing raw keys.

Two primitives

  1. Derive many accounts: each salt yields a distinct, independent account controlled by the same signer.
  2. Link with delegate actors: a delegate actor lets one account's keys authorize transactions on another.

Many accounts, one owner

The account address is a deterministic function of the salt (plus wallet code and initial actors). Pass a distinct salt to derive independent sub-accounts for per-app, per-user, or per-purpose isolation. Each is deployed independently on its first transaction.

Derive sub-accountsts
import { privateKeyToAccount, generatePrivateKey } from 'viem/accounts'
import { newSmartAccount } from 'viem/eip8130'
const owner = privateKeyToAccount(generatePrivateKey())
// saltFor is your own stable, human-meaningful salt derivation.
const main = newSmartAccount({ signer: owner, salt: saltFor('main') })
const trading = newSmartAccount({ signer: owner, salt: saltFor('trading') })
const savings = newSmartAccount({ signer: owner, salt: saltFor('savings') })
main.address !== trading.address // distinct accounts, same owner key

Linking with delegate actors

A delegate actor authorizes signatures that are valid for another account to act for this account. Add key.delegate(main.address) to a sub-account and the primary account's keys can drive it: a “controlled by” link, without sharing private keys.

Authorize a delegatets
import {
actorScope,
authorizeActor,
key,
sendTransaction,
} from 'viem/eip8130'
// On the sub account, authorize the primary account as a delegate.
const link = await subAccount.change(
[authorizeActor(key.delegate(main.address), { scope: actorScope.operator })],
{ chainId: client.chain.id, sequence: Number(sequence) },
)
const hash = await sendTransaction(client, {
account: subAccount,
accountChanges: [link],
calls: [],
gas: 200_000n,
})

Once linked, wrap the primary signer with toDelegateSigner and build a handle that signs for the sub-account through the delegate authenticator:

Drive the sub-accountts
import {
key,
sendTransaction,
toAccount,
toDelegateSigner,
} from 'viem/eip8130'
// Wrap main's admin signer so it authenticates through the delegate authenticator.
const delegateSigner = toDelegateSigner({
delegateAccount: main.address,
nestedSigner: main.signer, // an admin (scope 0) owner of main
})
// Drive subAccount.address with main's key via the delegate authenticator.
const subAsDelegate = toAccount({
signer: delegateSigner,
authenticator: delegateSigner.authenticator, // DelegateAuthenticator
address: subAccount.address,
actorId: key.delegate(main.address).actorId, // non-K1: needed for scope reads
})
const hash = await sendTransaction(client, {
account: subAsDelegate,
calls: [{ to: recipient, value: 1n }],
gas: 200_000n,
})

Revoking a link

Revoke the delegate actor to unlink a sub-account at any time:

Unlinkts
import { key, revokeActor } from 'viem/eip8130'
const unlink = await subAccount.change(
[revokeActor(key.delegate(main.address))],
{ chainId: client.chain.id, sequence: Number(sequence) },
)