Guide

Recipes

Short, end-to-end checklists that combine the earlier chapters. Each links back to the chapter with the full detail.

Passkey wallet from zero

  1. Create a WebAuthn credential and wrap it as a signer.
  2. Build the account with newSmartAccount (the address is deterministic; no pre-deploy needed).
  3. Send the first transaction: the account deploys on first use.
Passkey onboardingts
import {
createWebAuthnCredential,
toWebAuthnAccount,
} from 'viem/account-abstraction'
import {
newSmartAccount,
sendTransaction,
toWebAuthnSigner,
} from 'viem/eip8130'
const credential = await createWebAuthnCredential({ name: 'vibes' })
const account = newSmartAccount({
signer: toWebAuthnSigner(toWebAuthnAccount({ credential })),
})
// Deploys on first use; the passkey signs sender_auth.
const hash = await sendTransaction(client, {
account,
calls: [{ to: recipient, value: 1n }],
gas: 300_000n,
})

Detail: Creating accounts and Signing.

Gasless first transaction

  1. Point a payer client at the Vibenet ERC-8168 endpoint.
  2. Call sendSponsoredCalls: the payer co-signs and broadcasts (default mode: 'send').
  3. Confirm any token charge before re-signing.
Sponsored sendts
import { createPayerClient, sendSponsoredCalls } from 'viem/eip8168'
// Vibenet: payer co-signs and broadcasts (default mode: 'send')
const payerClient = createPayerClient({
url: 'https://vibes.base.org/api/vibenet/account/payer',
})
const { transactionHash: hash } = await sendSponsoredCalls(client, {
account, // signs sender_auth
payerClient, // co-signs payer_auth + submits
calls: [{ to: recipient, data }],
gas: 2_000_000n,
context: { flow: 'transact' },
})

Detail: Gas & payers.

Issue a spend-limited session key

  1. Author a policy config (token limits + allowlists) and bind it with defineSessionPolicy.
  2. Authorize the PolicyManager as a K1 operational actor (key.k1(manager)) and the key with POLICY | NONCE in one change (always grant nonce scope). No install step: just use the key next.
  3. Hand the key to the app; it spends via executeCall within its committed policy.

Detail: Session keys.

Rotate a compromised key

  1. Read the next local config sequence with getConfigSequence (use the local field).
  2. Authorize the replacement and revoke the old key in one change.
  3. Land it as a config-only transaction.
Atomic rotationts
import {
actorScope,
authorizeActor,
key,
revokeActor,
sendTransaction,
} from 'viem/eip8130'
const rotate = await account.change(
[
authorizeActor(key.k1('0xnewOwner…'), { scope: actorScope.operator }),
revokeActor(key.k1('0xoldOwner…')),
],
{ chainId: client.chain.id, sequence },
)
await sendTransaction(client, { account, accountChanges: [rotate], calls: [], gas: 200_000n })

Detail: Keys & scopes.

High-throughput sends

  1. Use nonce.randomChannel() for independent, out-of-order lanes.
  2. Or use nonce.nonceless(...) for fully parallel, retry-safe sends bounded by expiry.

Detail: Nonces & throughput.

Take an account multichain

  1. Register native chains with register8130Chains and spread eip8130ChainConfig into the chain so core client.sendTransaction submits native AA transactions; the same address works everywhere.
  2. On non-8130 chains, drive it through ERC-4337 with toSmartAccount.
  3. Rotate keys once with a chainId 0 change and apply it everywhere.

Detail: Multichain.

Sign a message (ERC-1271)

  1. Sign with account.signMessage (or account.signTypedData): the result is a multichain SignedMessageEnvelope signature.
  2. Verify with core client.verifyMessage, or use validateSignature to learn which actor and scope signed.
Sign and verifyts
// Multichain ERC-1271 signature (valid on every chain by default).
const signature = await account.signMessage({ message: 'gm' })
// Verify via core ERC-1271 (the account's isValidSignature):
const valid = await client.verifyMessage({
address: account.address,
message: 'gm',
signature,
})

Detail: Signing.