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
- Derive many accounts: each
saltyields a distinct, independent account controlled by the same signer. - Link with delegate actors: a
delegateactor 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.
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 keyLinking 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.
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:
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:
import { key, revokeActor } from 'viem/eip8130'
const unlink = await subAccount.change( [revokeActor(key.delegate(main.address))], { chainId: client.chain.id, sequence: Number(sequence) },)