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.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- account-abstraction Installs to ~/.agents/skills/account-abstraction/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install account-abstraction The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project account-abstraction Installs to .agents/skills/account-abstraction/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/account-abstraction.zipunzip -o account-abstraction.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) account-abstraction PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
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
- You only need a connect-wallet button and standard EOA signing: use wallet-integration
- The question is plain contract logic unrelated to accounts: use solidity-contracts
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.
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
| Goal | Default | Why |
|---|---|---|
| Sponsor gas or batch calls in your dapp for any wallet | EIP-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 wallet | An audited account (Safe, Kernel, Nexus, Coinbase Smart Wallet, Light/Simple) via a client SDK | Writing your own account is audit-grade work |
| Upgrade existing EOAs to batch and sponsorship | EIP-7702 delegation to an audited implementation + EntryPoint v0.8 | Keeps the user’s address |
| Custom validation (session keys, spending limits, multisig) | ERC-7579 module on a modular account | Reuse audited account core; write only the module |
| Custom account from scratch | Only with an audit budget and a reason none of the above fits | Validation 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)
| EntryPoint | Address |
|---|---|
| v0.6 (legacy) | 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 |
| v0.7 | 0x0000000071727De22E5E9d8BAf0edAc6f37da032 |
| v0.8 (7702 support) | 0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108 |
Confirm with cast code <address> --rpc-url $RPC (network needed) before relying on it.
How a UserOperation flows
- Client builds the op:
sender,nonce(from EntryPointgetNonce(sender, key)),factory/factoryDataif undeployed,callData(the account’sexecute), gas fields, optional paymaster fields, and a dummy signature for estimation. eth_estimateUserOperationGason the bundler; paymaster stub data first if sponsored.- Paymaster service signs (
pm_getPaymasterData) if sponsored. - Account owner signs the
userOpHash(EntryPoint, chain ID and op fields hashed together). eth_sendUserOperation, then polleth_getUserOperationReceipt.- On chain: EntryPoint calls
validateUserOpon the account,validatePaymasterUserOpon the paymaster, then executescallData, thenpostOp.
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.successis 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,BALANCEof others,CREATE(except via the factory), and similar environment reads. Express time windows throughvalidUntil/validAfterpacked intovalidationData. - 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
| Type | Validates | Main risk |
|---|---|---|
| Verifying (sponsor) | Signature from your backend over op hash + validity window | Backend signs ops it should not; no per-user limits |
| ERC-20 | User has allowance and balance; charges in postOp | Price 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’sverifyMessage/verifyTypedDataon a public client handle both;ecrecoveralone 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
| Code | Meaning | Usual fix |
|---|---|---|
| AA10 | Sender already constructed | Drop factory/factoryData |
| AA13 | initCode failed or out of gas | Check factory call and verificationGasLimit |
| AA21 | Did not pay prefund | Fund the account or add a paymaster |
| AA23 / AA24 | Validation reverted / signature error | Wrong signer, wrong hash (EntryPoint version or chain) |
| AA25 | Invalid account nonce | Fetch nonce from EntryPoint with the right key |
| AA31 / AA33 | Paymaster deposit too low / validation reverted | Top up deposit, inspect paymaster policy |
| AA40 / AA41 | Over verification gas limit | Re-estimate; add margin |
Testing
- Unit: call
validateUserOpfrom 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.yaml
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.