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 { 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 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,
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:

Drive the sub-accountts
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:

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