---
name: build-with-viem-eip8130
description: >-
  Build apps on native account abstraction (EIP-8130) and payer
  sponsorship (ERC-8168) using the viem experimental module. Use when writing
  code that creates or operates 8130 smart accounts, authorizes session-key
  actors or policies, sends batched calls, sponsors gas with a payer, or wires a
  frontend/script against the Vibenet devnet.
---

# Build with viem — EIP-8130 (native account abstraction)

EIP-8130 puts account abstraction **in the protocol**. Accounts are portable
across EVM chains, support multiple signer types (secp256k1, P-256, WebAuthn),
key rotation without changing address, scoped session-key actors, on-chain
policies, and native ERC-8168 gas sponsorship. The tooling lives in viem's
`experimental/eip8130` module.

## Where the code lives

- **EIP-8130 spec:** https://eip.tools/eip/8130 (payer standard:
  https://eip.tools/eip/8168).
- **viem fork/branch:** `github.com/chunter-cb/viem`, branch `feat/eip-8130`
  (module: `src/experimental/eip8130`, payer: `src/experimental/eip8168`;
  docs: `site/pages/experimental/eip8130`). The experimental module is not yet
  in npm `viem`, so install the fork branch.
- **RPC endpoint:**
  - Vibenet devnet — `https://rpc.vibes.base.org`, chain id `84538453`.

Add the fork branch as your `viem` dependency, then import from
`viem/experimental/eip8130` (and `viem/experimental/eip8168`):

```bash
bun add "viem@github:chunter-cb/viem#feat/eip-8130"
```

```ts
import { newSmartAccount8130, sendCalls8130 } from "viem/experimental/eip8130";
```

## Core concepts

- **Account** — a viem-style account object with `.address` (deterministic,
  CREATE2-derived), `.create()`/`.createChange` (first-tx deploy change), and
  `signTransaction`. Create via `newSmartAccount8130` / `to8130Account` /
  `toEoa8130Account`.
- **Actor** — a signer authorized on an account, with a **scope** bitfield and
  optional **policy**. Built with `key`, `authorizeActor`, `revokeActor`.
- **Scope** (`actorScope`) — `scopeUnrestricted` (0x00) is admin. Bits:
  `sender` `policy` `nonce` `selfPayer` `sponsorPayer`. A policy-bearing actor
  must be restricted (non-zero scope), or `authorizeActor` throws.
- **Policy** — on-chain rules (e.g. `SessionPolicy`: per-token spend limits +
  call scopes). Built with `defineSessionPolicy` + `encodeSessionPolicyConfig`.
- **Calls** — a batch of `{ to, value?, data? }` executed atomically, with
  optional signed top-level `metadata`.
- **Payer (ERC-8168)** — a service that co-signs `payer_auth` to pay gas
  (sponsored or in ERC-20). Client: `createPayerClient`.

## Minimal end-to-end: create a smart account and send a batch

```ts
import {
  createPublicClient, http, parseEther, toHex,
  newSmartAccount8130, sendCalls8130, estimateGas8130,
  waitForTransactionReceipt8130, allPhasesSucceeded,
  key, getEip8130Deployment,
} from "viem/experimental/eip8130";

const chainId = 84538453; // Vibenet devnet
const client = createPublicClient({
  chain: { id: chainId, name: "vibenet", nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
           rpcUrls: { default: { http: ["https://rpc.vibes.base.org"] } } },
  transport: http(),
});

// 1) Pick a signer. K1 from a private key, or toP256Signer / toWebAuthnSigner.
const signer = key.k1("0x…privateKey");

// 2) Deterministic account — address exists before any tx.
const account = newSmartAccount8130({ signer });

// 3) First tx deploys the account AND runs the batch atomically.
//    Fund account.address first (faucet), then estimate + send.
const calls = [{ to: "0x…recipient", value: parseEther("0.001") }];
const gas = await estimateGas8130(client, {
  sender: account.address,
  accountChanges: [account.createChange],
  calls: [calls],
});
const hash = await sendCalls8130(client, {
  account,
  accountChanges: [account.createChange], // omit on subsequent txs
  calls,
  metadata: toHex("invoice #4242"),
  gas: (gas * 120n) / 100n,
});

// 4) Wait and check every phase (create + each call) succeeded.
const receipt = await waitForTransactionReceipt8130(client, { hash });
if (!allPhasesSucceeded(receipt)) throw new Error("a phase reverted");
```

Canonical contract addresses per chain come from `getEip8130Deployment(chainId)`
(or `canonicalEip8130Deployment`): `accountConfiguration`, `accounts.*`,
`authenticators.*`, `policies.{manager,sessionPolicy}`.

## Authorize a policy-gated session actor

```ts
import {
  key, authorizeActor, actorScope,
  defineSessionPolicy, encodeSessionPolicyConfig, getEip8130Deployment,
} from "viem/experimental/eip8130";

const dep = getEip8130Deployment(chainId);
const policyConfig = encodeSessionPolicyConfig({
  tokenLimits: [{ token: usdv, limit: 100_000_000n, period: 604_800n }], // 100 USDV / week
  callScopes: [{ target: usdv, selectorRules: [{ selector: "0xa9059cbb" }] }], // transfer only
});
const session = defineSessionPolicy({
  account: account.address, policy: dep.policies.sessionPolicy,
  policyConfig, manager: dep.policies.manager, validUntil: 1_900_000_000n,
});

// Session key is restricted (SCOPE_SENDER) + carries the policy (SCOPE_POLICY bit set automatically).
const sessionSigner = key.p256({ x: "0x…", y: "0x…" });
const change = authorizeActor(sessionSigner, {
  scope: actorScope.sender,
  expiry: 1_900_000_000n,
  policy: session.actorPolicy,
});
// Include `change` in accountChanges of a sendCalls8130 signed by an admin actor.
```

## Sponsor gas with a payer (ERC-8168)

```ts
import { createPayerClient, sendSponsoredCalls } from "viem/experimental/eip8168";

const payer = createPayerClient({ url: "https://payer.vibes.base.org" });
const hash = await sendSponsoredCalls(client, {
  account, payer, calls,
  accountChanges: [account.createChange],
  context: { flow: "transact" }, // budgets free grants per (sender, flow)
});
```

## Reading account state (viem actions)

All take `(client, params)` and read the on-chain `AccountConfiguration`:
`getActorConfig8130`, `isActor8130`, `getPolicy8130`, `getSessionSpend8130`,
`getLockStatus8130` / `isLocked8130`, `getConfigSequence8130`,
`getTransactionCount8130`, `getTransaction8130`, `getTransactionReceipt8130`,
`waitForTransactionReceipt8130`.

## Account creation modes

- `newSmartAccount8130({ signer })` — new CREATE2 smart account (most common).
- `to8130Account({ signer, userSalt, code, initialActors, authenticator, accountConfigAddress })`
  — full control over salt / initial actors.
- `to8130Account({ signer, address, authenticator })` — a configured (non-default)
  actor on an existing/delegated account.
- `toEoa8130Account(signer)` — an EOA acting as its own default K1 actor
  (raw 65-byte sig, EIP-7702 delegation via `account.delegate(impl)`).

## Locking

`lockCall` / `initiateUnlockCall` build `applySignedLockChanges` calls; hash the
change to sign with `hashLockChange8130` (`lockChangeTypehash`). `lockCall`
requires `unlockDelay >= 1`.

## Gotchas

- Fund `account.address` **before** the first tx — the deploy+batch pays gas
  from the account (unless a payer sponsors it).
- Only the **first** tx includes `account.createChange`; later txs omit it.
- A policy actor must have a non-zero scope; admin (scope 0) + policy is rejected.
- Contract addresses are bytecode-derived — the deployed system must be compiled
  with the same solc as `canonicalEip8130Deployment`, or account creation fails
  with "create address mismatch".
- Chains must be 8130-aware: use `register8130Chains` / `is8130Enabled` when
  operating outside the built-in `eip8130ChainIds`.

## Reference

- Full API surface: `src/experimental/eip8130/index.ts` on the viem fork branch.
- viem docs: `site/pages/experimental/eip8130` on the fork branch — creating
  accounts, sending txs, session keys, batching, payer services, and more.
