Repository navigation
feat: EIP-8130 (native account abstraction) + ERC-8168 payer services - #5004
chunter-cb wants to merge 103 commits into
Conversation
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.
commit: |
There was a problem hiding this comment.
💡 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() |
There was a problem hiding this comment.
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 👍 / 👎.
| const transaction = await prepareTransactionRequest(client, { | ||
| account, | ||
| calls: built.calls, |
There was a problem hiding this comment.
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 👍 / 👎.
| const resolvedValidBefore = | ||
| validBefore ?? | ||
| (expiresIn !== undefined | ||
| ? BigInt(Date.now() + expiresIn * 1000) | ||
| : undefined) |
There was a problem hiding this comment.
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 👍 / 👎.
| request = { | ||
| type: aaTransactionType, | ||
| sender: account_, | ||
| nonceKey: Number(nonceKey), |
There was a problem hiding this comment.
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 👍 / 👎.
|
|
||
| # 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. |
There was a problem hiding this comment.
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 👍 / 👎.
| ...toTransactionBody(transaction), | ||
| payer ?? '0x', | ||
| senderAuth ?? '0x', | ||
| payerAuth ?? '0x', |
There was a problem hiding this comment.
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 👍 / 👎.
| if (sender && from && sender !== from) | ||
| throw new BaseError( | ||
| `\`sender\` (${sender}) and \`from\` (${from}) must agree when both are set.`, | ||
| ) |
There was a problem hiding this comment.
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 👍 / 👎.
| { | ||
| text: 'EIP-8130', | ||
| items: [ | ||
| { | ||
| text: 'Overview', | ||
| link: '/eip8130', |
There was a problem hiding this comment.
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 👍 / 👎.
| const settle = async <T>( | ||
| fn: (payer: PayerClient) => Promise<T>, | ||
| ): Promise<T[]> => { | ||
| const results = await Promise.allSettled(payers.map(fn)) |
There was a problem hiding this comment.
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 👍 / 👎.
| const paymasterService = capabilities?.paymasterService | ||
| const terms: GetTermsReturnType = await payerClient.getTerms({ | ||
| chainId: numberToHex(chainId), |
There was a problem hiding this comment.
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.
…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
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-8130AA_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)
eip8130ChainConfig: spread it into a chain and coreclient.sendTransactionsubmits a nativeAA_TX_TYPEfor an 8130 account (gas auto-estimated), while coreclient.getTransactionReceipt/client.waitForTransactionReceiptreturn theeip8130receipt fields. Mirrors the TempochainConfigfold..extend(eip8130Actions())→client.eip8130.*(full read/write suite),.extend(eip8168Actions())→client.payer.*(payer flow). Namespaced because viem's protected actions require core-conforming signatures.Clientfirst-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:
prepareTransactionRequestfills the tx and returns offers oncapabilities.paymentOptions;sendTransactionsubmits the chosencapabilities.paymentOption;sendTransactionSyncawaits the receipt.gasEstimator: 'self'), takingmax(ours, payer)so an added payment phase never under-sizes the tx;gasEstimator: 'payer'trusts the service outright.sendSponsoredCallsengine (offer selection, phase-0 construction, re-quote/re-sign) is reused underneath.API consistency
8130suffix and bundler-era naming:sendCalls→sendTransaction,prepareTransaction→prepareTransactionRequest, plussendTransactionSync.keystoreAbi,keystoreAddress,keystoreCalls.estimateGasaccepts the same flat-or-nestedcallsshape assendTransaction(shared normalizer).validBeforeis configurable (nowto anchor to block time,expiryWindow), defaulting toDate.now()+ 20s.Testing
src/eip8130/**andsrc/eip8168/**(encoding, factory args, signing, nonce/scope logic, decorators, chain-config fold, capability payer paths).pnpm check:typesis green.Status / notes
viem/eip8130for iteration, with a clean path to promote once the spec + canonical contracts stabilize.main(merge, not rebase) to preserve history.