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
- Create a WebAuthn credential and wrap it as a signer.
- Build the account with
newSmartAccount(the address is deterministic; no pre-deploy needed). - 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
- Point a payer client at the Vibenet ERC-8168 endpoint.
- Call
sendSponsoredCalls: the payer co-signs and broadcasts (defaultmode: 'send'). - 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
- Author a policy config (token limits + allowlists) and bind it with
defineSessionPolicy. - Authorize the PolicyManager as a K1 operational actor (
key.k1(manager)) and the key withPOLICY | NONCEin one change (always grant nonce scope). No install step: just use the key next. - Hand the key to the app; it spends via
executeCallwithin its committed policy.
Detail: Session keys.
Rotate a compromised key
- Read the next local config sequence with
getConfigSequence(use thelocalfield). - Authorize the replacement and revoke the old key in one change.
- 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
- Use
nonce.randomChannel()for independent, out-of-order lanes. - Or use
nonce.nonceless(...)for fully parallel, retry-safe sends bounded by expiry.
Detail: Nonces & throughput.
Take an account multichain
- Register native chains with
register8130Chainsand spreadeip8130ChainConfiginto the chain so coreclient.sendTransactionsubmits native AA transactions; the same address works everywhere. - On non-8130 chains, drive it through ERC-4337 with
toSmartAccount. - Rotate keys once with a
chainId 0change and apply it everywhere.
Detail: Multichain.
Sign a message (ERC-1271)
- Sign with
account.signMessage(oraccount.signTypedData): the result is a multichainSignedMessageEnvelopesignature. - Verify with core
client.verifyMessage, or usevalidateSignatureto 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.