Skip to content

feat: EIP-8130 (native account abstraction) + ERC-8168 payer services - #5004

Open
chunter-cb wants to merge 103 commits into
wevm:mainfrom
chunter-cb:feat/eip-8130-production
Open

chunter-cb wants to merge 103 commits into
wevm:mainfrom
chunter-cb:feat/eip-8130-production

Conversation

@chunter-cb

@chunter-cb chunter-cb commented Aug 13, 2026 •

Copy link
Copy Markdown

Summary

Adds two new (additive) modules to viem for EIP-8130 native account abstraction and its ERC-8168 payer-service companion. No existing public APIs are modified; the design folds into viem's native surface wherever possible.

  • viem/eip8130 — accounts, actions, and utilities for the EIP-8130 AA_TX_TYPE (0x79) native-AA transaction: smart-account creation, actor management (authorize/revoke, scopes, expiry), session keys, sub-accounts, receipts/metadata, and a portable ERC-4337 path (via bundler + EntryPoint) for chains without native 8130 support.
  • viem/eip8168 — payer-service client for sponsoring EIP-8130 transactions (discover/aggregate payer sources, carry account changes through sponsored calls).

Both modules are documented under site/pages/eip8130/** and wired into the docs sidebar.

Three ways to integrate (native-first)

  • Core actions via eip8130ChainConfig: spread it into a chain and core client.sendTransaction submits a native AA_TX_TYPE for an 8130 account (gas auto-estimated), while core client.getTransactionReceipt / client.waitForTransactionReceipt return the eip8130 receipt fields. Mirrors the Tempo chainConfig fold.
  • Namespaced decorators: .extend(eip8130Actions()) → client.eip8130.* (full read/write suite), .extend(eip8168Actions()) → client.payer.* (payer flow). Namespaced because viem's protected actions require core-conforming signatures.
  • Standalone actions: every action is importable directly and takes a Client first-arg.

Payer services as a capability of the fill

Payment is modeled as a component of filling a transaction rather than a separate "choose terms" endpoint:

  • prepareTransactionRequest fills the tx and returns offers on capabilities.paymentOptions; sendTransaction submits the chosen capabilities.paymentOption; sendTransactionSync awaits the receipt.
  • Gas estimation: defaults to our own node estimate (gasEstimator: 'self'), taking max(ours, payer) so an added payment phase never under-sizes the tx; gasEstimator: 'payer' trusts the service outright.
  • The tested sendSponsoredCalls engine (offer selection, phase-0 construction, re-quote/re-sign) is reused underneath.

API consistency

  • Dropped the 8130 suffix and bundler-era naming: sendCalls → sendTransaction, prepareTransaction → prepareTransactionRequest, plus sendTransactionSync.
  • The system contract is the Keystore throughout (formerly "Account Configuration"): keystoreAbi, keystoreAddress, keystoreCalls.
  • estimateGas accepts the same flat-or-nested calls shape as sendTransaction (shared normalizer).
  • Nonce-free validBefore is configurable (now to anchor to block time, expiryWindow), defaulting to Date.now() + 20s.

Testing

  • Unit tests colocated across src/eip8130/** and src/eip8168/** (encoding, factory args, signing, nonce/scope logic, decorators, chain-config fold, capability payer paths). pnpm check:types is green.
  • Network-gated e2e harnesses (Base Sepolia ERC-4337, vibenet native flow) were validated during development but are kept out of the PR/CI.

Status / notes

  • Opened as a draft: EIP-8130 is not yet frozen, so this lands as viem/eip8130 for iteration, with a clean path to promote once the spec + canonical contracts stabilize.
  • Synced to latest main (merge, not rebase) to preserve history.

chunter-cb added 30 commits June 9, 2026 14:44
Adds a ground-up `viem/experimental/eip8130` module implementing the
EIP-8130 (`AA_TX_TYPE` = 0x7b) wire format:

- serializeTransaction8130 / parseTransaction8130 (round-trip)
- sender (AA_TX_TYPE) and payer (AA_PAYER_TYPE) signature hashes
- account_changes encoders/decoders: create, config (actor changes with
  scope/expiry/policy), and delegation entries
- 2D nonce, nonce-free mode, and self-pay/sponsored payer modes
- constants, actor/authenticator types, and structural assertions
Adds signTransaction8130 producing sender_auth (EOA raw signature or
ECRECOVER_AUTHENTICATOR || signature for configured actors) and, for
sponsored transactions, payer_auth bound to the resolved sender.
Adds computeAddress8130 (and the DEPLOYMENT_HEADER builder) for create
entries: actors_commitment over sorted [actorId || authenticator], an
effective salt over user_salt, and CREATE2 derivation against
ACCOUNT_CONFIG_ADDRESS. Address constants are placeholders pending the
canonical base/eip-8130 deployment and are overridable per call.
Adds account-configuration support:
- canonical authenticator set constants (k1 native sentinel; p256,
  passkey, delegate placeholders) and actorIdFromAddress
- hashActorChanges8130 (SignedActorChanges ABI digest) and
  signActorChanges8130, returning a ready config account-change entry
- IAccountConfiguration / IAuthenticator / precompile ABIs

Shares the actor-change `data` encoder with the transaction serializer.
Enables routing the same account between 8130 and non-8130 chains:
- is8130Enabled chain registry + predicate (AA_TX_TYPE vs ERC-4337)
- toFactoryArgs8130 / encodeCreateAccountData: ERC-4337 factory args
  via the AccountConfiguration contract (address matches computeAddress8130)
- encodeApplySignedActorChangesData: portable any-chain config-change path
Lets the same EIP-8130 account run on non-8130 chains via a bundlerClient,
backed by the canonical BackwardCompatibleERC4337Account wallet:
- toSmartAccount8130: encodeCalls via executeBatch(Call[]), getFactoryArgs
  via AccountConfiguration.createAccount, getAddress via computeAddress8130,
  and signatures in authenticator||data form for authenticateActor validation
- erc4337AccountAbi for the wallet (executeBatch/validateUserOp/isValidSignature)
- erc1167Bytecode helper for the minimal-proxy deployment code
…alls)

High-level surface over the 8130 primitives:
- to8130Account: create()/change()/delegate()/signTransaction() lifecycle
- key builders (k1/p256/passkey/delegate) + actorIdFromPublicKey
  (keccak256(x||y)) and scope/policy helpers (toScope, authorizeActor,
  revokeActor, encodePolicyData)
- sendCalls8130 / prepareTransaction8130: prepare (fees + nonce), sign,
  serialize and submit an AA_TX_TYPE transaction
Ties payer sponsorship into the 8130 transaction flow:
- createPayerClient: payer_* JSON-RPC (getTerms/sendTransaction/
  signTransaction/getBalance/getSponsorshipOptions/getCapabilities)
- buildSponsoredCalls: phase construction from terms (full sponsorship,
  token payment, required calls) + ERC-20 transfer encoding
- sendSponsoredCalls: getTerms -> build -> prepare -> sender co-sign
  (payer_auth empty) -> payer_sendTransaction / payer_signTransaction
- full ERC-8168 types + error codes; viem/experimental/eip8168 subpath
- add eip8130Deployments registry (AccountConfiguration, account impls,
  authenticator contracts) with getEip8130Deployment(chainId)
- replace placeholder accountConfigAddress / defaultAccountAddress and
  canonical p256/passkey/delegate authenticators with the deployed values
…tion

The deployed AccountConfiguration contract decodes an authorizeActor change's
`data` via abi.decode(data, (ActorConfig, bytes)), but our codec used RLP per the
EIP-8130 text. RLP data reverts at abi.decode, so applySignedActorChanges could
not land on-chain.

Consolidate on a single ABI encoder/decoder (actorChangeData.ts) used by the
config-change digest, the applySignedActorChanges calldata, and the native-wire
serialize/parse paths. Validated end-to-end on Base Sepolia: account creation and
a P-256 session-key authorization both land and read back as expected.

Adds gated (PRIVATE_KEY) integration scripts for the two on-chain flows.
toSmartAccount8130 now signs the raw userOpHash (no EIP-191 prefix) to
match on-chain validateUserOp, with a pluggable `sign` and configurable
`stubData` for non-k1 authenticators. Adds Base Sepolia scripts that drive
ERC-4337 flows, including self-bundled create+execute via EntryPoint.handleOps
(AccountConfiguration as the factory, no staking).
Add encodeSignedActorChangesSignature (+ signedActorChangesMagic): builds a
BackwardCompatibleERC4337Account userOp signature carrying SignedActorChanges[]
(abi.encode(magic, [{ changes, auth }, ...])). Applying the signed change chain
during validateUserOp authorizes the op — no separate op signature. Sets apply
in order, enabling chained rotations. Adds a Base Sepolia script that creates an
account and authorizes a P-256 actor in the validation phase in one self-bundled
userOp.
New Deploy.s.sol + CreateAccounts.s.sol run:
  AccountConfiguration: 0xb0198a714872EE5bfDF829e7986DB5C5899a6b50
  DefaultAccount:        0x124b52d5D57a76ed064c414975beA11Beffe0251
  DefaultHighRateAccount:0x13dD0F222cCF60B7C08a95C2d1FcC85A38DD675D
  ERC4337Account:        0x9748aeA1e1762E50a4d8927777FeDB63A2Ef06C0
  DelegateAuthenticator: 0xE67D299Ff3F0a185398B6C5a28998696969265d7
…ployment

The deployed BackwardCompatibleERC4337Account decodes the signed-actor-changes
signature as (bytes32 magic, SignedActorChanges[] changeSets, bytes opAuth) and
authenticates the op via the trailing opAuth blob over userOpHash. Re-add the
opAuth parameter to encodeSignedActorChangesSignature to match, and pass the
correct AccountConfiguration address through the self-bundle scripts.

- constants: point accountConfigAddress / defaultAccountAddress at the latest
  Base Sepolia deployment
- signedActorChangesSignature: add required opAuth arg + update tests
- scripts: pass accountConfigAddress explicitly; selfBundleRotateP256 signs
  userOpHash as opAuth and uses sequence 1 (createAccount seeds localSequence=1)
…d tx demo

Move the EIP-8130 dev/integration scripts into a dedicated scripts/eip8130/
folder and collapse the vitest include to a single glob so new scripts are
picked up automatically. Add build8130Transaction: an offline demo that builds,
signs, serializes, and parses an EIP-8130 transaction, printing the JSON object,
the RLP envelope, and the decoded 13-field wire layout. The demo highlights that
a call is only { to, data } (no per-call value).
Add optional `value` to `AaCall` and `encodeWalletCalls`, which routes
value-bearing phases through the account's `executeBatch` (pluggable via
`encodeExecute`) while passing value-less calls through as `[to, data]`.
Wire into `sendCalls8130` and reject non-zero `value` at serialization.
- Rename payer_getSponsorshipOptions → payer_getOptions (method + types)
- Rename maxCost → paymentAmount in PayerTokenOption and buildSponsoredCalls
- Fix SponsorshipOption: type 'full_sponsorship' → 'sponsored', tokenSymbol
  → symbol, estimatedCost → estimatedAmount; rename to PayerOption
- Fix error codes: -32600–32608 range → -32001–32009 (JSON-RPC impl-defined
  range; the old codes conflicted with JSON-RPC 2.0 protocol errors)
- Add overhead to PayerGasEstimate; add usdRate + refund to PayerTokenOption
- Add RefundPolicy type; add feeRecipient, callPolicy, refund to
  PayerChainCapabilities; remove non-spec requiredChainId from PayerConditions
- Add payer_fillTransaction method to PayerClient
- Fix expiry semantics: terms.expiry is relative (seconds from now); convert
  to absolute on-chain timestamp in sendSponsoredCalls, clamped to
  conditions.maxExpiry / minExpiry
- Update tests: use relative durations, freeze time for deterministic expiry
  assertions, add payer_getOptions mapping test
Add EIP-8130-aware JSON-RPC actions matching the base node RPC surface:
- getTransactionCount8130: read the 2D channel nonce via the
  eth_getTransactionCount nonce_key extension. The NonceManager precompile
  is not callable via eth_call, so this replaces the reverting readContract
  path in prepareTransaction8130.
- estimateGas8130: eth_estimateGas for AA_TX_TYPE with declared sender/payer
  auth scheme + size and an optional sponsoring payer (MAX_AUTH_SIZE guarded).
- getTransactionReceipt8130 + parseEip8130ReceiptFields/allPhasesSucceeded:
  surface payer, phaseStatuses, and metadata off the receipt (graceful when
  absent on older nodes).

Also brings in the session-policy helpers (policies.ts), signer adapters
(signers.ts), serialization/parse alignment, and the vibenet devnet
deployment with their tests.
Update the ERC-8168 module to the revised spec: offer-based terms with a
top-level gasEstimate, the sponsored/sponsored_declined/token discriminants,
the single -32000 payerRejected error envelope with a string data.code, and
count/asset balance limits. Adds parsePayerError and refreshes the tests.
Reads the local and multichain config-change sequences from the
AccountConfiguration system contract. Always supply the active
chain's deployment address so vibenet and Base Sepolia use the
correct contract rather than a hardcoded constant.
…esses

- getPayerSignatureHash8130: include `payer` field in the RLP body to
  match the Rust node's payer_signature_hash (which encodes all fields
  including the payer slot). Previously the payer field was excluded,
  producing a hash mismatch that caused "actor is not bound" errors.

- deployments: replace stale per-chain address objects with a single
  canonicalEip8130Deployment derived from solc 0.8.33 via Deploy.s.sol.
  Both baseSepoliaDeployment and vibenetDevnetDeployment now reference
  the same canonical set (accountConfiguration 0xC659…, P256 0x3AE1…,
  etc.). The execution client enshrines these addresses; using any other
  accountConfiguration causes "create address mismatch" on account
  creation.
…e 0x7b formatting

- Add getTransaction8130 action: fetches an EIP-8130 tx by hash,
  flattens the nested `tx` body returned by the node, injects the
  request hash (absent from the RPC response), and returns a fully-typed
  Transaction8130 object with all EIP-8130 fields.

- Add waitForTransactionReceipt8130 action: polls until an EIP-8130 tx
  is mined using getTransactionReceipt8130 (which surfaces the
  EIP-8130-specific receipt fields). Skips the replacement-detection
  path since EIP-8130 uses 2D nonces.

- getTransaction: flatten type 0x7b responses inline so the standard
  path also works for EIP-8130 txs (injects hash, maps nested body
  fields to the expected flat shape).

- waitForTransactionReceipt: skip candidates with no hash — can occur
  when a block contains a type 0x7b tx that the generic parser can't
  fully deserialize.

- formatters/transaction: register '0x7b' -> 'eip8130' in transactionType.

- Add vibenet 6-part integration test covering: EOA plain tx, EOA owner
  change, EOA with new key, smart account create, smart account tx, and
  smart account key rotation.
…& 8)

Tests 7 and 8 cover the full P256 lifecycle on vibenet:
  7. P256 smart account — createAccount + ETH send (P-256 signer)
  8. P256 smart account — follow-up send with same P-256 key (no redeploy)

Uses toP256Signer + ox/P256.randomPrivateKey to generate a fresh key each
run and verifies end-to-end: fund → create → send → balance check.
Add accountChanges + calls parameters to EstimateGas8130Parameters.
When present, the request is sent as a complete EIP-8130 tx body so the
node routes through the real executor simulation (required for create
account-change pricing and accurate per-phase call cost).

When omitted, the existing simplified mode is used unchanged (backward
compatible). The serialize helper converts typed AaAccountChange objects
to the plain JSON the node's eth_estimateGas deserialiser expects.
Forward senderAuthScheme/senderAuthSize to the node even when
accountChanges/calls are present, so the node can price intrinsic auth
gas correctly for accounts not yet deployed on-chain (where it can't
infer the scheme from account state).
…t, key.webAuthn

- key.webAuthn: alias for key.passkey to match deployment naming convention
- newSmartAccount8130(params): factory that auto-derives actor from signer type
  (K1/P256/WebAuthn detected from publicKey + authenticator), generates a random
  salt, defaults code to canonical DefaultAccount, and exposes createChange as a
  property for convenient first-tx usage
- toEoa8130Account(signer): wraps a K1 EOA for EIP-8130 txs using the implicit
  self-actor path (raw 65-byte sig, no authenticator prefix, no smart contract)
- Export canonicalEip8130Deployment for downstream consumers
…EOAs

Split To8130AccountParameters into two shapes:
- Smart account (userSalt + code + initialActors): derives CREATE2 address,
  exposes create()
- Delegated EOA (address only): binds to a known address, no salt/code/actors
  needed, create() throws with a clear message directing to delegate(impl)

This fixes the DX smell where EOA delegation required dummy userSalt/code/
initialActors. Now: to8130Account({ signer, address: eoaAddress }) is all
that's needed for the delegated-EOA path; the first tx uses delegate(impl)
in accountChanges and change([...]) to add actors.
…change + sign

Adds `delegate(target)` and `change(actorChanges, opts)` to
`toEoa8130Account` so a bare EOA can build its first-tx delegation and
actor-config changes without needing a second account handle.

`signTransaction` still uses the implicit self-actor path: no `from`
field, raw 65-byte K1 signature, sender recovered via ecrecover.
Resolves the sender (`from`) of an EIP-8130 transaction: returns
`transaction.from` when present, otherwise (EOA path, where the wire
omits `from`) recovers it via ecrecover over the sender signature hash
computed with `from` empty — matching the node.

Lets relayers / payers bind to the resolved sender for EOA-path txs.
…overrides

All protocol contracts are deterministic CREATE2 (identical on every chain), so
the "defaults to Base Sepolia / may differ per chain" wording on the
authenticators, DefaultAccount, and SessionPolicy addresses was misleading.

Also document why the enshrined keystore is not overridable while the unaudited,
extensible policy contracts (PolicyManager / SessionPolicy) still accept an
override.
…-> prepareTransactionRequest; add sendTransactionSync

Aligns the EIP-8130 send surface with viem-native naming. Drops the
ERC-5792/4337-flavored `sendCalls` / `prepareTransaction` names (and the
collision with core `sendCalls`) now that this path is native
`AA_TX_TYPE` submission rather than a bundler flow.

- `sendCalls` -> `sendTransaction`, `SendCallsParameters` -> `SendTransactionParameters`
- `prepareTransaction` -> `prepareTransactionRequest` (+ Parameters)
- add `sendTransactionSync` via core `eth_sendRawTransactionSync`, returning
  the AA receipt with `eip8130` fields (payer/phaseStatuses/metadata)

Fill/sign/submit behavior is unchanged. Updates the eip8168 caller, all
JSDoc references, unit tests, and docs. Hard rename, no aliases
(module is experimental, pre-merge).
…nRequest/sendTransaction)

Folds the ERC-8168 payer surface behind the native fill -> send verbs, so
"choosing terms" is a component of filling a transaction rather than a
standalone endpoint:

- add `prepareTransactionRequest`: fills the tx and returns solicited offers
  as `capabilities.paymentOptions` (via `capabilities.paymasterService`)
- add `sendTransaction`: submits the chosen `capabilities.paymentOption`,
  delegating to the tested `sendSponsoredCalls` engine (offer selection,
  phase-0 build, re-quote/re-sign) unchanged
- rename the low-level `payer_*` RPC types to `Payer*`
  (SendTransaction*/SignTransaction* -> Payer*) to free the native names and
  clarify they are the payer RPC layer

`sendSponsoredCalls` stays as the reusable engine. Adds capability-face unit
tests; existing payer tests unchanged.
Sync variant of the capability-centric sponsored send: submits via the payer
(`payer_sendTransaction`, which returns the hash) and awaits the EIP-8130
receipt, threading the resolved `validBefore` for fast expiry detection.
Always `mode: "send"` (co-sign-only `"sign"` has nothing to await). Returns
the payer send result plus the `eip8130` receipt.
Add `eip8130Actions()` (client.eip8130.*) and `eip8168Actions({ payerClient? })`
(client.payer.*) so the native AA_TX_TYPE and payer flows ride an extended
client. Namespaced (mirroring the tempo decorator) because viem's `.extend`
requires protected core actions (sendTransaction, estimateGas, etc.) to conform
to core signatures, which the EIP-8130 variants intentionally do not. The payer
decorator can bind a default payerClient so callers don't repeat it.
Add a tempo-style `chainConfig` that registers a `transactionReceipt`
formatter, so on an EIP-8130-enabled chain core `client.getTransactionReceipt`
and `client.waitForTransactionReceipt` natively return the AA fields (`payer`,
`phaseStatuses`, `metadata`) under `receipt.eip8130`.

Only the receipt is folded. The send path (phased calls, required gas, 2D
nonce, actor-auth envelopes, payer auth) does not map onto core
`client.sendTransaction`, and `eth_getTransactionByHash` returns a non-standard
nested body, so those stay on the `client.eip8130.*` decorator.
Receipts now fold into core via eip8130ChainConfig, so remove the redundant
client.eip8130.getTransactionReceipt method and point users to the chain config.
Make the EIP-8130 account a viem local account (type 'local', source
'eip8130') and add a `prepareTransactionRequest` hook to eip8130ChainConfig.
On a chain that spreads the config, core `client.sendTransaction({ account,
calls, gas })` now resolves the AA body (2D nonce, fees, scope-driven nonce
mode, phased-call encoding) via the hook and lets the account serialize + sign
the AA_TX_TYPE (0x79) envelope, so no eth_fillTransaction/getTransactionCount
fallback runs.

Covers the self-pay path; sponsored sends stay on client.eip8168.* (the payer
signer is not part of a core request).
The core sendTransaction fold now auto-estimates gas via the EIP-8130
eth_estimateGas extension when `gas` is omitted (threading senderActorId /
senderAuthAuthenticator hints), instead of requiring it. Passing a `payer`
through core sendTransaction now throws a redirect to
client.eip8130.sendTransaction (local payer) / client.eip8168.sendTransaction
(payer service), since sponsored settlement is off the raw-submit path.
The sponsored fill now defaults to our own node estimate (gasEstimator:
'self') and uses max(ours, payer) when the payer also quotes, so an added
payment phase never under-sizes the tx. gasEstimator: 'payer' trusts the
service outright; explicit `gas` overrides both.
AaCall.data is optional (defaults to '0x'), but the permission call
builders always populate it. Assert non-null at the decode sites so
check:types is green.
… senderAuthAuthenticator)

The estimateGas action never exposed `senderAuthVerifier`; the hint is
`senderAuthAuthenticator`. Copy-pasted doc examples would have thrown.
The system contract is now the Keystore. Renames the exported ABI
`accountConfigurationAbi` -> `keystoreAbi`, the `accountConfigCalls`
module -> `keystoreCalls`, and all prose/docstrings referencing
"Account Configuration"/`ACCOUNT_CONFIG_ADDRESS` to the Keystore.
On-chain function names and `keystoreAddress` are unchanged.
…ty payer API

- Overview: replace the "no .extend() decorator" note with the three
  integration styles (eip8130ChainConfig, eip8130Actions/eip8168Actions
  decorators, standalone actions).
- Sending: document core client.sendTransaction via eip8130ChainConfig
  (gas auto-estimated) alongside the standalone actions.
- Payer services: lead with the capability API (client.payer.prepare/
  send, gasEstimator self|payer max-of, sendTransactionSync); frame
  sendSponsoredCalls as the underlying engine.
- Receipts: note core receipts surface eip8130 fields via the chain config.
Nonce-free sends auto-compute validBefore from the client wall clock
(Date.now()) plus a 20s window. On chains whose block time is skewed
from the client, that can produce a born-expired tx. Add optional `now`
(anchor to e.g. the block timestamp) and `expiryWindow` overrides;
Date.now() + nonceFreeMaxExpiryWindow remains the default.
estimateGas accepted nested-only (AaCalls) while sendTransaction accepts
a flat AaCall[] OR nested phases. Extract a shared toPhases normalizer and
use it in estimateGas, sendTransaction, and the chainConfig hook so every
entry point takes the same calls shape.
@jxom
jxom marked this pull request as ready for review August 27, 2026 21:18
@pkg-pr-new

pkg-pr-new Bot commented Aug 27, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/viem@5004

commit: 7992598

@jxom
jxom marked this pull request as draft August 27, 2026 21:23

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 799259823f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

for (let attempt = 0; ; attempt++) {
transaction.calls = phases
transaction.gas = gas
transaction.validBefore = computeValidBefore()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve the prepared nonce-free expiry

When a configured actor omits its local scope as recommended and the on-chain lookup identifies it as nonce-free-only, prepareTransactionRequest correctly selects nonceKeyMax and replaces the initial 0n expiry with a valid window. This assignment then calls computeValidBefore() again using only account.scope, overwriting the resolved expiry with 0n when the offer has no maxExpiry; serialization consequently throws because nonce-free transactions require a nonzero validBefore, so sponsored sends from such session keys never reach the payer.

Useful? React with 👍 / 👎.

Comment on lines +256 to +258
const transaction = await prepareTransactionRequest(client, {
account,
calls: built.calls,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Encode value-bearing calls before sponsored signing

When any user call has a nonzero value, buildSponsoredCalls leaves that intent in built.calls and this path passes it directly into the wire transaction instead of applying encodeWalletCalls as the native send path does. assertTransaction then rejects the transaction during signing because EIP-8130 wire calls cannot carry value, making ERC-8168 sponsorship unusable for ETH transfers despite the public parameter type accepting AaCall.value.

Useful? React with 👍 / 👎.

Comment thread src/eip8130/nonce.ts
Comment on lines +114 to +118
const resolvedValidBefore =
validBefore ??
(expiresIn !== undefined
? BigInt(Date.now() + expiresIn * 1000)
: undefined)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Cap relative nonce-free expiries to the admission window

For nonce.nonceless({ expiresIn }), values above 20 seconds are accepted unchanged, and the documented example even supplies 600 seconds. The module's own nonceFreeMaxExpiryWindow states that nonce-free transactions must be admitted within a 20-second window, while sendTransaction preserves an explicitly supplied validBefore, so transactions produced by this helper with a longer duration are rejected by the node rather than sent successfully.

Useful? React with 👍 / 👎.

Comment thread src/eip8130/actions/estimateGas.ts Outdated
request = {
type: aaTransactionType,
sender: account_,
nonceKey: Number(nonceKey),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve full-width nonce keys during estimation

In full-body mode, converting the uint256 nonce key to a JavaScript number loses precision above 2^53 - 1. This affects almost every key returned by the advertised nonce.randomChannel() helper, causing eth_estimateGas to receive a different or invalid channel key while the subsequently signed transaction retains the original bigint; serialize the key without passing through Number or reject unsupported widths explicitly.

Useful? React with 👍 / 👎.

Comment thread site/pages/eip8130.mdx Outdated

# EIP-8130 (Native Account Abstraction) [Overview]

[EIP-8130](https://github.com/base/eip-8130) is a native account-abstraction transaction type (`AA_TX_TYPE`, `0x79`). Every account is a smart account governed by an onchain **Keystore** contract, and transactions are sent directly to the chain — no bundler, no EntryPoint. Viem exposes the full flow through the `viem/eip8130` entrypoint.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove em dashes from the new documentation

This page and the newly added EIP-8130 guides contain many em dashes, starting here, despite the site documentation convention explicitly prohibiting them; rewrite these occurrences using commas, colons, parentheses, or separate sentences.

AGENTS.md reference: AGENTS.md:L17-L18

Useful? React with 👍 / 👎.

Comment on lines +149 to +152
...toTransactionBody(transaction),
payer ?? '0x',
senderAuth ?? '0x',
payerAuth ?? '0x',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject transactions without sender authentication

The public serializer documents senderAuth as required, but this fallback encodes an empty auth field when it is absent. Such an envelope cannot authenticate any sender and is rejected by the node, so direct callers receive apparently valid serialized bytes instead of an actionable local error; enforce the requirement in assertTransaction before encoding.

Useful? React with 👍 / 👎.

Comment on lines +216 to +219
if (sender && from && sender !== from)
throw new BaseError(
`\`sender\` (${sender}) and \`from\` (${from}) must agree when both are set.`,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Compare sender addresses case-insensitively

When callers provide both from and sender for the same Ethereum address using different checksum casing, this string comparison treats them as different and rejects the estimate. Address casing does not change the address value, so use the repository's address-equality helper or normalize both values before enforcing agreement.

Useful? React with 👍 / 👎.

Comment thread site/vocs.config.ts
Comment on lines +1648 to +1653
{
text: 'EIP-8130',
items: [
{
text: 'Overview',
link: '/eip8130',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Give EIP-8130 pages their own sidebar route

These entries link to /eip8130 pages but are nested under the /experimental sidebar key. The rest of this configuration scopes each sidebar by its route prefix, so visiting the new /eip8130 pages does not select this sidebar, while experimental pages unexpectedly contain links for a graduated top-level section; move the group under a /eip8130 sidebar key.

Useful? React with 👍 / 👎.

Comment thread src/eip8168/aggregate.ts
Comment on lines +85 to +88
const settle = async <T>(
fn: (payer: PayerClient) => Promise<T>,
): Promise<T[]> => {
const results = await Promise.allSettled(payers.map(fn))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bound slow payer sources during aggregation

Although the aggregate promises that a slow source will not block the rest, Promise.allSettled does not return until every source settles. A payer whose request hangs indefinitely therefore prevents available offers from every healthy payer from being returned; apply a per-source timeout or otherwise settle degraded sources independently.

Useful? React with 👍 / 👎.

Comment on lines +157 to +159
const paymasterService = capabilities?.paymasterService
const terms: GetTermsReturnType = await payerClient.getTerms({
chainId: numberToHex(chainId),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor the paymaster capability opt-in

The API says capabilities.paymasterService opts a fill into soliciting payer offers, but payer_getTerms is called unconditionally even when that capability is absent. A caller requesting an ordinary fill therefore makes an unexpected external payer request and fails if that service is unavailable; skip negotiation and return no payment options unless the capability was supplied.

Useful? React with 👍 / 👎.

…t address sync

- Add SignedMessageEnvelope signing path: replaySafeHash / envelope digest
  utils, signMessageEnvelope / signTypedDataEnvelope, ERC-6492 counterfactual
  wrap, and a validateSignature read action. Wire toAccount / newSmartAccount /
  toSmartAccount signMessage & signTypedData to emit ERC-1271 envelopes so core
  client.verifyMessage works. Docs (signing-messages) + tests.
- abis: replace stale verifySignature with validateSignature (matches Keystore).
- Fold dataSuffix (+ client.dataSuffix) into top-level, signed `metadata` on the
  core eip8130ChainConfig send path; document metadata as a top-level tx field.
- Sync base/eip-8130 contract changes (PR wevm#95-100): actorScope reorder +
  SENDER→OPERATOR, getActor→getActorWithPolicy, importAccount()/computeImportDigest,
  SignedAccountChangeBatch typehash, CoinbaseSmartWalletV2 proxy docs.
- Update canonical contract addresses (keystore, default/highRate accounts,
  delegate authenticator, policy manager/session policy) and regenerate the
  commitmentOf reference vector.
TRUSTED_EXECUTOR is gone after base/eip-8130 wevm#101. key.trustedExecutor
now aliases key.k1 so PolicyManager and EntryPoint authorize as live
k1 operational actors.
@chunter-cb
chunter-cb marked this pull request as ready for review September 3, 2026 00:20
…lls, K1 payer auth

Core eip8130 now targets the Keystore-free AA tx: calls are [to, value, data],
payer_auth is K1_AUTHENTICATOR || sig, and valid_before accepts ms. Keystore
accounts, actors, policies, and session helpers move to experimental/keystore.
Token offers quote a single WAD `rate` (token units per 1e18 wei) per the
current ERC-8168 draft, with advisory `paymentGas`. Rejections carry
`shortfall` and fresh `options`, and add SENDER_BALANCE_INSUFFICIENT and
EXECUTION_REVERTED. `methods` lists the supported co-sign methods.
…ative, cost)

- payer_sendTransaction / payer_signTransaction take `transaction`;
  signTransaction returns `{ payerAuth }` and is asked before the sender
  signs (named sender), so a rejection never costs a signature
- drop tokenCharged; GAS_EXCEEDS_LIMIT -> COST_EXCEEDS_LIMIT with
  cost{estimated,limit}; EXECUTION_REVERTED revert diagnostic
- TokenPaymentOffer -> TokenOffer; "native" token (value-transfer phase 0)
- phase 0 priced from rate at the tx's own max_cost and re-priced on GAS_TOO_LOW
- balance provider{name}; getSponsorshipBalance kinds filter
- estimateGas: forward maxFeePerGas/maxPriorityFeePerGas and default them
  to 8-byte placeholders, so the node prices the real fee-field bytes
  instead of returning a short, zero-headroom estimate
- chainConfig: pass the filled fees into the estimate
- constants: add protocolNonceCost (nonce_key 0 is free)
- estimateGas docs: bare senderAuth must be 65 bytes; open payerAuth
  is stubbed when absent
- live edge tests: call value is no longer reserved at admission;
  elapsed validity windows are rejected by eth_estimateGas

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants