Account Abstraction skill for Codex

Gasless, batched transactions that bundlers accept. Codex first picks the lightest route, wallet_sendCalls, an audited smart account, EIP-7702 delegation or a 7579 module, before writing any account code. It builds UserOperations with viem's account-abstraction clients, follows ERC-7562 validation rules, puts spend policies in front of paymaster signing, verifies smart-account signatures with ERC-1271 and 6492, and checks the inner success flag of every receipt.

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- account-abstraction

Installs to ~/.agents/skills/account-abstraction/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $account-abstraction, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Implements ERC-4337 smart accounts and the flows around them - EntryPoint v0.7 and v0.8, UserOperation construction and gas estimation through bundler RPCs, validateUserOp rules from ERC-7562, verifying and ERC-20 paymasters, counterfactual deployment via factories, EIP-7702 delegated EOAs, ERC-7579 modules, ERC-1271 and ERC-6492 signatures, and viem account-abstraction clients. Use when asked to add gasless transactions, sponsor gas, build or integrate a smart wallet, batch calls for users, debug an AA error code, or choose between 4337, 7702 and wallet_sendCalls.

Use it when

  • Users should transact without holding ETH for gas
  • A dapp needs batched calls such as approve plus swap in one step
  • A smart account, module or paymaster contract is being written or reviewed
  • Bundlers reject UserOperations with AA error codes

Use something else when

How to check the result

A testnet run completes a first-op deployment, a batched call and a sponsored op through the real bundler, the receipt success flag is checked, and a denied sponsorship is refused by policy.

The mistake it prevents

Reverting on a bad signature in validateUserOp instead of returning SIG_VALIDATION_FAILED, which breaks gas estimation with dummy signatures.

~/.agents/skills/account-abstraction/SKILL.md

--- name: account-abstraction description: Implements ERC-4337 smart accounts and the flows around them - EntryPoint v0.7 and v0.8, UserOperation construction and gas estimation through… license: MIT ---

AA work is correct when the account validates signatures without breaking the bundler simulation rules, the paymaster cannot be drained by unpaid or griefing operations, signatures verify for undeployed and deployed accounts alike, and the client waits for the UserOperation receipt and its inner success flag, not just the bundler’s acceptance.

Decide the approach first

GoalDefaultWhy
Sponsor gas or batch calls in your dapp for any walletEIP-5792 wallet_sendCalls with a paymaster capability (ERC-7677)The user’s wallet does the AA work; least code for you
Give users an embedded smart walletAn audited account (Safe, Kernel, Nexus, Coinbase Smart Wallet, Light/Simple) via a client SDKWriting your own account is audit-grade work
Upgrade existing EOAs to batch and sponsorshipEIP-7702 delegation to an audited implementation + EntryPoint v0.8Keeps the user’s address
Custom validation (session keys, spending limits, multisig)ERC-7579 module on a modular accountReuse audited account core; write only the module
Custom account from scratchOnly with an audit budget and a reason none of the above fitsValidation bugs mean drained wallets

Read AGENTS.md, package.json and the contracts folder to see which SDK, account implementation and EntryPoint version the repo already targets. Never mix EntryPoint versions in one flow: the UserOperation format and hash differ.

Reference addresses (canonical deployments; verify on your chain)

EntryPointAddress
v0.6 (legacy)0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789
v0.70x0000000071727De22E5E9d8BAf0edAc6f37da032
v0.8 (7702 support)0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108

Confirm with cast code <address> --rpc-url $RPC (network needed) before relying on it.

How a UserOperation flows

  1. Client builds the op: sender, nonce (from EntryPoint getNonce(sender, key)), factory/factoryData if undeployed, callData (the account’s execute), gas fields, optional paymaster fields, and a dummy signature for estimation.
  2. eth_estimateUserOperationGas on the bundler; paymaster stub data first if sponsored.
  3. Paymaster service signs (pm_getPaymasterData) if sponsored.
  4. Account owner signs the userOpHash (EntryPoint, chain ID and op fields hashed together).
  5. eth_sendUserOperation, then poll eth_getUserOperationReceipt.
  6. On chain: EntryPoint calls validateUserOp on the account, validatePaymasterUserOp on the paymaster, then executes callData, then postOp.

Client side with viem

import { createPublicClient, http, parseEther } from "viem";
import { createBundlerClient, toCoinbaseSmartAccount } from "viem/account-abstraction";
import { privateKeyToAccount } from "viem/accounts";
import { baseSepolia } from "viem/chains";

const client = createPublicClient({ chain: baseSepolia, transport: http(process.env.RPC_URL) });
const owner = privateKeyToAccount(process.env.OWNER_KEY as `0x${string}`);
const account = await toCoinbaseSmartAccount({ client, owners: [owner] });

const bundler = createBundlerClient({
  account,
  client,
  transport: http(process.env.BUNDLER_URL),
  // paymaster: createPaymasterClient({ transport: http(process.env.PAYMASTER_URL) }),
});

const hash = await bundler.sendUserOperation({
  calls: [
    { to: "0xRecipient...", value: parseEther("0.001") },
    { to: TOKEN, abi: erc20Abi, functionName: "transfer", args: ["0xTo...", 1_000_000n] },
  ],
});
const receipt = await bundler.waitForUserOperationReceipt({ hash });
if (!receipt.success) throw new Error(`UserOperation reverted in tx ${receipt.receipt.transactionHash}`);
  • The bundler can accept an op whose inner call later reverts; the transaction succeeds and receipt.success is false. Always check it.
  • Other account helpers exist for Safe, Kernel, Nexus and 7702 accounts in viem or permissionless.js; use the one matching the account the product actually deploys.
  • Bundler and paymaster URLs carry API keys: keep them server-side or restrict them by origin and policy in the provider dashboard.

Writing validation logic

function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds)
    external returns (uint256 validationData)
{
    if (msg.sender != address(entryPoint)) revert NotEntryPoint();
    validationData = _validateSignature(userOp, userOpHash); // 0 = ok, 1 = SIG_VALIDATION_FAILED
    if (missingAccountFunds != 0) {
        (bool ok, ) = payable(msg.sender).call{value: missingAccountFunds}("");
        (ok); // EntryPoint verifies payment itself
    }
}

Rules from ERC-7562 that bundlers enforce during simulation (break them and ops are dropped):

  • Return SIG_VALIDATION_FAILED (1) for a bad signature instead of reverting, so gas estimation with dummy signatures works.
  • No banned opcodes in validation: TIMESTAMP, NUMBER, BLOCKHASH, GASPRICE, BALANCE of others, CREATE (except via the factory), and similar environment reads. Express time windows through validUntil/validAfter packed into validationData.
  • Storage access limited to the account’s own storage and slots associated with it (for example mapping(address account => ...) in another contract).
  • Validation must not depend on state another op in the same bundle can change.

Prefer inheriting an audited base (eth-infinitism BaseAccount, OpenZeppelin’s account contracts in recent releases, or a modular account) to writing these rules from memory.

Paymasters

TypeValidatesMain risk
Verifying (sponsor)Signature from your backend over op hash + validity windowBackend signs ops it should not; no per-user limits
ERC-20User has allowance and balance; charges in postOpPrice oracle manipulation; user drains allowance before postOp
  • Backend policy before signing: allowed target contracts and selectors, per-user and global spend caps, short validUntil, rate limits. Log every sponsorship.
  • Paymaster deposit lives in the EntryPoint (depositTo); stake is required for paymasters that access storage. Monitor the deposit and alert before it runs dry.
  • Funding or withdrawing paymaster deposits on mainnet is a money movement: confirm the amount and destination with the user before sending.

EIP-7702

An EOA signs an authorization to set its code to a delegate implementation; afterwards calls to the EOA run that code while the address and private key stay the same.

  • Delegate only to audited, immutable implementations; the delegate has full control of the account’s assets.
  • Storage persists across re-delegation. Implementations must use namespaced storage (ERC-7201) to avoid collisions when a user switches implementation.
  • The private key still has full power: 7702 adds features, it does not remove the key’s authority, so key loss or theft is unchanged.
  • With EntryPoint v0.8, the UserOperation carries the authorization so the first op can delegate and execute together.

Signatures from smart accounts

  • Verify signatures with ERC-1271 (isValidSignature) for deployed accounts and ERC-6492 wrapping for undeployed (counterfactual) ones. viem’s verifyMessage/verifyTypedData on a public client handle both; ecrecover alone does not.
  • Sign-In with Ethereum and permit flows must accept 1271 signatures or smart-account users cannot log in or approve.

Common AA error codes

CodeMeaningUsual fix
AA10Sender already constructedDrop factory/factoryData
AA13initCode failed or out of gasCheck factory call and verificationGasLimit
AA21Did not pay prefundFund the account or add a paymaster
AA23 / AA24Validation reverted / signature errorWrong signer, wrong hash (EntryPoint version or chain)
AA25Invalid account nonceFetch nonce from EntryPoint with the right key
AA31 / AA33Paymaster deposit too low / validation revertedTop up deposit, inspect paymaster policy
AA40 / AA41Over verification gas limitRe-estimate; add margin

Testing

  • Unit: call validateUserOp from the EntryPoint address (vm.prank(address(entryPoint))) with valid, invalid and expired signatures; assert return values, not reverts.
  • Integration: anvil with the EntryPoint deployed and a local bundler (Alto, Rundler or similar) in its own container; run full flows including deployment-on-first-op, batch calls, sponsorship denial and a reverted inner call.
  • Testnet run with the real bundler and paymaster provider before mainnet.

Output contract

**Approach:** 7702 + EntryPoint v0.8, delegate: <audited impl @ address>
**Bundler / paymaster:** <provider>, keys server-side, policy: allowlisted selectors, $X/user/day
**Flows tested:** first op with authorization, batch approve+swap, sponsorship denied, inner revert surfaced
**Signatures:** SIWE and permit verified via ERC-1271/6492
**Open risks:** paymaster deposit alerting at 0.5 ETH not yet wired

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Account Abstraction"
  short_description: "Gasless, batched transactions that bundlers accept"
  default_prompt: "$account-abstraction Add sponsored gas for our mint function using a paymaster"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc