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 { newSmartAccount8130 } from 'viem/experimental/eip8130'
const owner = privateKeyToAccount(generatePrivateKey())
// saltFor is your own stable, human-meaningful salt derivation.const main = newSmartAccount8130({ signer: owner, salt: saltFor('main') })const trading = newSmartAccount8130({ signer: owner, salt: saltFor('trading') })const savings = newSmartAccount8130({ 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, sendCalls8130,} from 'viem/experimental/eip8130'
// On the sub account, authorize the primary account as a delegate.const link = await subAccount.change( [authorizeActor(key.delegate(main.address), { scope: actorScope.sender })], { chainId: client.chain.id, sequence: Number(sequence) },)
const hash = await sendCalls8130(client, { account: subAccount, accountChanges: [link], calls: [], gas: 200_000n,})Once linked, build a handle that signs for the sub-account using the primary signer through the delegate authenticator:
import { canonicalAuthenticators, sendCalls8130, to8130Account,} from 'viem/experimental/eip8130'
// Drive subAccount.address with main's key via the delegate authenticator.const subAsDelegate = to8130Account({ signer: main.signer, authenticator: canonicalAuthenticators.delegate, address: subAccount.address,})
const hash = await sendCalls8130(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/experimental/eip8130'
const unlink = await subAccount.change( [revokeActor(key.delegate(main.address))], { chainId: client.chain.id, sequence: Number(sequence) },)