### Install @account-abstraction/contracts Source: https://context7.com/eth-infinitism/account-abstraction/llms.txt Install the npm package to use the contracts in your own project. ```bash yarn add @account-abstraction/contracts # or npm install @account-abstraction/contracts ``` -------------------------------- ### Install Account Abstraction Contracts Source: https://github.com/eth-infinitism/account-abstraction/blob/develop/README.md Use this command to add the account abstraction contracts library to your project. ```bash yarn add @account-abstraction/contracts ``` -------------------------------- ### Clone Repository and Install Dependencies Source: https://github.com/eth-infinitism/account-abstraction/blob/develop/README.md Clone the account-abstraction repository and install project dependencies using yarn. ```bash git clone https://github.com/eth-infinitism/account-abstraction.git cd account-abstraction yarn install ``` -------------------------------- ### Get and Increment Nonces with NonceManager Source: https://context7.com/eth-infinitism/account-abstraction/llms.txt Use `getNonce` for sequential nonces per account and key. Use `incrementNonce` to manually pre-increment a nonce key, which can save gas on the first UserOp. ```solidity IEntryPoint entryPoint = IEntryPoint(0x4337084d9e255ff0702461cf8895ce9e3b5ff108); // Get next sequential nonce for key=0 (standard usage) uint256 nonce = entryPoint.getNonce(mySmartWallet, 0); // Use a non-zero key for a parallel nonce lane (e.g., for session keys) uint192 sessionKey = 0x1; uint256 sessionNonce = entryPoint.getNonce(mySmartWallet, sessionKey); // Manually pre-increment a nonce key to initialize it (saves gas on first real UserOp) // Called directly from the account's EOA, not via a UserOperation entryPoint.incrementNonce(sessionKey); // Full nonce layout in PackedUserOperation.nonce: // nonce = (uint192(key) << 64) | uint64(sequence) ``` -------------------------------- ### EntryPointSimulations Source: https://context7.com/eth-infinitism/account-abstraction/llms.txt Off-chain simulation utilities for validating UserOperations and estimating gas costs. ```APIDOC ## EntryPointSimulations Off-chain simulation utilities used by bundlers to validate UserOperations and estimate gas before submitting to the mempool. ### `simulateValidation(PackedUserOperation userOp)` Validates a single UserOperation. Reverts on failure. Returns `ValidationResult` containing preOpGas, prefund, and accountValidationData. ### `simulateHandleOp(PackedUserOperation userOp, address targetCall, bytes callData)` Simulates the full execution of a UserOperation, including an optional subsequent call. Ignores signature errors during simulation. Returns `ExecutionResult` containing preOpGas, paid gas, and target call success status. #### Example Usage: ```solidity import "@account-abstraction/contracts/interfaces/IEntryPointSimulations.sol"; IEntryPointSimulations epSim = IEntryPointSimulations(0x4337084d9e255ff0702461cf8895ce9e3b5ff108); // Validate a single UserOperation (reverts on failure) IEntryPointSimulations.ValidationResult memory result = epSim.simulateValidation(userOp); // result.returnInfo.preOpGas — gas used in validation // result.returnInfo.prefund — required ETH deposit // result.returnInfo.accountValidationData — packed validationData from account // result.senderInfo.stake — account's current stake // result.paymasterInfo.stake — paymaster's current stake (if any) // Simulate full execution including callData (ignores signature errors) IEntryPointSimulations.ExecutionResult memory execResult = epSim.simulateHandleOp( userOp, address(0), // no extra target call after the userOp "" ); // execResult.preOpGas — gas used before execution // execResult.paid — total gas cost in wei // execResult.targetSuccess — true if the optional target call succeeded ``` ``` -------------------------------- ### Deploy SimpleAccount with SimpleAccountFactory Source: https://context7.com/eth-infinitism/account-abstraction/llms.txt Use `SimpleAccountFactory` to deploy `SimpleAccount` proxies via `CREATE2`. The `initCode` is used in the first `UserOperation` to deploy the smart wallet. ```solidity import "@account-abstraction/contracts/accounts/SimpleAccountFactory.sol"; SimpleAccountFactory factory = new SimpleAccountFactory(entryPoint); // Pre-compute counterfactual address (before deployment) uint256 salt = 0; address walletAddress = factory.getAddress(ownerEOA, salt); // => deterministic address based on owner + salt; same result before and after deployment // Fund the counterfactual address so it can pay gas on first UserOp walletAddress.call{value: 0.1 ether}(""); // Build initCode for the first UserOperation that deploys the wallet bytes memory initCode = abi.encodePacked( address(factory), abi.encodeCall(SimpleAccountFactory.createAccount, (ownerEOA, salt)) ); // Set userOp.initCode = initCode; EntryPoint calls factory through SenderCreator // On subsequent ops, set initCode = "" (wallet is already deployed) ``` -------------------------------- ### Simulate UserOperation Validation and Execution Source: https://context7.com/eth-infinitism/account-abstraction/llms.txt Use `EntryPointSimulations` for off-chain validation and gas estimation. `simulateValidation` checks UserOp validity, while `simulateHandleOp` simulates full execution. ```solidity import "@account-abstraction/contracts/interfaces/IEntryPointSimulations.sol"; IEntryPointSimulations epSim = IEntryPointSimulations(0x4337084d9e255ff0702461cf8895ce9e3b5ff108); // Validate a single UserOperation (reverts on failure) IEntryPointSimulations.ValidationResult memory result = epSim.simulateValidation(userOp); // result.returnInfo.preOpGas — gas used in validation // result.returnInfo.prefund — required ETH deposit // result.returnInfo.accountValidationData — packed validationData from account // result.senderInfo.stake — account's current stake // result.paymasterInfo.stake — paymaster's current stake (if any) // Simulate full execution including callData (ignores signature errors) IEntryPointSimulations.ExecutionResult memory execResult = epSim.simulateHandleOp( userOp, address(0), // no extra target call after the userOp "" ); // execResult.preOpGas — gas used before execution // execResult.paid — total gas cost in wei // execResult.targetSuccess — true if the optional target call succeeded ``` -------------------------------- ### Deploy Entrypoint Contract Source: https://github.com/eth-infinitism/account-abstraction/blob/develop/README.md Deploy the EntryPoint contract to a specified network using the hardhat deploy command. ```bash hardhat deploy --network {net} ``` -------------------------------- ### SimpleAccountFactory Source: https://context7.com/eth-infinitism/account-abstraction/llms.txt A reference factory for deploying SimpleAccount proxies using CREATE2, enabling deterministic address generation. ```APIDOC ## SimpleAccountFactory A reference factory that deploys `SimpleAccount` proxies via `CREATE2`. Used as the `initCode` factory in a `UserOperation` to deploy a new smart wallet on first use. ### `createAccount(address owner, uint256 salt)` Deploys a `SimpleAccount` contract. ### `getAddress(address owner, uint256 salt)` Pre-computes the counterfactual address of the `SimpleAccount` before deployment. #### Example Usage: ```solidity import "@account-abstraction/contracts/accounts/SimpleAccountFactory.sol"; SimpleAccountFactory factory = new SimpleAccountFactory(entryPoint); // Pre-compute counterfactual address (before deployment) uint256 salt = 0; address walletAddress = factory.getAddress(ownerEOA, salt); // => deterministic address based on owner + salt; same result before and after deployment // Fund the counterfactual address so it can pay gas on first UserOp walletAddress.call{value: 0.1 ether}(""); // Build initCode for the first UserOperation that deploys the wallet bytes memory initCode = abi.encodePacked( address(factory), abi.encodeCall(SimpleAccountFactory.createAccount, (ownerEOA, salt)) ); // Set userOp.initCode = initCode; EntryPoint calls factory through SenderCreator // On subsequent ops, set initCode = "" (wallet is already deployed) ``` ``` -------------------------------- ### UserOperationLib: Gas and Encoding Utilities Source: https://context7.com/eth-infinitism/account-abstraction/llms.txt Provides library functions for unpacking gas limits and fees from a `PackedUserOperation`, calculating effective gas prices, parsing paymaster fields, hashing UserOperations, and encoding paymaster signatures. Ensure `UserOperationLib` is correctly imported and used with `using UserOperationLib for PackedUserOperation;`. ```Solidity import "@account-abstraction/contracts/core/UserOperationLib.sol"; using UserOperationLib for PackedUserOperation; // Unpack individual gas fields from packed bytes32 uint256 verifyGas = userOp.unpackVerificationGasLimit(); // high 128 bits of accountGasLimits uint256 callGas = userOp.unpackCallGasLimit(); // low 128 bits of accountGasLimits uint256 maxFee = userOp.unpackMaxFeePerGas(); // low 128 bits of gasFees uint256 maxPrio = userOp.unpackMaxPriorityFeePerGas(); // high 128 bits of gasFees // Effective gas price (capped at maxFeePerGas) uint256 price = userOp.gasPrice(); // = min(maxFee, maxPrio + block.basefee) // Unpack paymaster fields from paymasterAndData (address pm, uint256 pmVerifyGas, uint256 pmPostOpGas) = UserOperationLib.unpackPaymasterStaticFields(userOp.paymasterAndData); // Hash the operation (same algorithm as EntryPoint.getUserOpHash minus chain/ep wrapping) bytes32 innerHash = UserOperationLib.hash(userOp, bytes32(0)); // Encode a paymaster signature as the optional suffix to paymasterAndData bytes memory pmSig = UserOperationLib.encodePaymasterSignature(paymasterECDSASig); // Append pmSig to paymasterAndData; it is excluded from the userOpHash automatically ``` -------------------------------- ### Run Tests Source: https://github.com/eth-infinitism/account-abstraction/blob/develop/README.md Execute the test suite for the project using the yarn test command. ```bash yarn test ``` -------------------------------- ### Compile Contracts Source: https://github.com/eth-infinitism/account-abstraction/blob/develop/README.md Compile the smart contracts in the repository using the yarn compile command. ```bash yarn compile ``` -------------------------------- ### EntryPoint - getUserOpHash Source: https://context7.com/eth-infinitism/account-abstraction/llms.txt Computes the canonical hash of a UserOperation, incorporating the EntryPoint address and chain ID. Accounts must sign this hash. ```APIDOC ## getUserOpHash ### Description Computes the canonical hash of a `UserOperation` (over all fields except `signature`), incorporating the EntryPoint address and chain ID. Accounts must sign this hash. ### Method `getUserOpHash` ### Parameters - `userOp` (UserOperation) - The UserOperation object for which to compute the hash. ### Response - `userOpHash` (bytes32) - The computed hash of the UserOperation. ### Request Example ```solidity import "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; IEntryPoint entryPoint = IEntryPoint(0x4337084d9e255ff0702461cf8895ce9e3b5ff108); bytes32 userOpHash = entryPoint.getUserOpHash(userOp); // Off-chain: sign userOpHash with the owner's private key (ethers.js example) // const sig = await owner.signMessage(ethers.utils.arrayify(userOpHash)); // userOp.signature = sig; // On-chain: verify inside _validateSignature function _validateSignature( PackedUserOperation calldata userOp, bytes32 userOpHash ) internal override returns (uint256 validationData) { address recovered = ECDSA.recover(userOpHash, userOp.signature); if (recovered != owner) return SIG_VALIDATION_FAILED; // = 1 return SIG_VALIDATION_SUCCESS; // = 0 } ``` ``` -------------------------------- ### EntryPoint - handleAggregatedOps Source: https://context7.com/eth-infinitism/account-abstraction/llms.txt Executes batches of UserOperations grouped by signature aggregator. This is used when accounts return an aggregator address from validateUserOp, allowing bundlers to combine signatures for gas savings. ```APIDOC ## handleAggregatedOps ### Description Executes batches of `UserOperation`s grouped by signature aggregator. Used when accounts return an aggregator address from `validateUserOp`, allowing bundlers to combine signatures for gas savings. ### Method `handleAggregatedOps` ### Parameters - `opsPerAgg` (IEntryPoint.UserOpsPerAggregator[]) - An array of UserOpsPerAggregator structures, where each structure contains user operations, the aggregator address, and a combined signature. - `bundlerAddress` (payable) - The address of the bundler. ### Request Example ```solidity import "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; IEntryPoint.UserOpsPerAggregator[] memory opsPerAgg = new IEntryPoint.UserOpsPerAggregator[](1); opsPerAgg[0] = IEntryPoint.UserOpsPerAggregator({ userOps: aggregatedOpsArray, aggregator: IAggregator(blsAggregator), signature: combinedBLSSignature }); entryPoint.handleAggregatedOps(opsPerAgg, payable(bundlerAddress)); ``` ``` -------------------------------- ### EntryPoint - depositTo / balanceOf / withdrawTo Source: https://context7.com/eth-infinitism/account-abstraction/llms.txt Manages the gas-prepayment deposit pool for accounts and paymasters. ETH can be deposited to prefund operations, and unused gas is refunded automatically. ```APIDOC ## depositTo / balanceOf / withdrawTo ### Description Manages the gas-prepayment deposit pool. Accounts and paymasters deposit ETH here to prefund operations; unused gas is refunded automatically after each bundle. ### Methods - `depositTo` - `balanceOf` - `withdrawTo` ### Parameters - `depositTo`: - `value` (ether) - The amount of ETH to deposit. - `balanceOf`: - `account` (address) - The address of the account to check the balance for. - `withdrawTo`: - `recipient` (payable) - The address to withdraw the funds to. - `amount` (ether) - The amount of ETH to withdraw. ### Request Example ```solidity IEntryPoint entryPoint = IEntryPoint(0x4337084d9e255ff0702461cf8895ce9e3b5ff108); // Deposit ETH for an account so it can pay its own gas entryPoint.depositTo{value: 0.5 ether}(mySmartWallet); // Check current deposit balance uint256 balance = entryPoint.balanceOf(mySmartWallet); // => 500000000000000000 (0.5 ETH) // Withdraw deposit back (called from the account itself or via a UserOperation) entryPoint.withdrawTo(payable(recipient), 0.1 ether); ``` ``` -------------------------------- ### Handle User Operations with EntryPoint Source: https://context7.com/eth-infinitism/account-abstraction/llms.txt Executes a batch of PackedUserOperations using the EntryPoint contract. This is typically called by a bundler after off-chain validation. Ensure the signature is valid for the userOpHash. ```solidity import "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; import "@account-abstraction/contracts/interfaces/PackedUserOperation.sol"; IEntryPoint entryPoint = IEntryPoint(0x4337084d9e255ff0702461cf8895ce9e3b5ff108); PackedUserOperation[] memory ops = new PackedUserOperation[](1); ops[0] = PackedUserOperation({ sender: mySmartWallet, nonce: entryPoint.getNonce(mySmartWallet, 0), initCode: "", callData: abi.encodeCall(SimpleAccount.execute, (recipient, 0.01 ether, "")), accountGasLimits: bytes32((uint256(100_000) << 128) | uint256(80_000)), preVerificationGas: 45_000, gasFees: bytes32((uint256(1.5e9) << 128) | uint256(30e9)), paymasterAndData: "", signature: signedByOwner // sign entryPoint.getUserOpHash(ops[0]) }); // Called by the bundler; beneficiary receives the gas fees entryPoint.handleOps(ops, payable(bundlerAddress)); // Emits: UserOperationEvent(userOpHash, sender, paymaster=0, nonce, success=true, actualGasCost, actualGasUsed) ``` -------------------------------- ### Execute and ExecuteBatch Calls Source: https://context7.com/eth-infinitism/account-abstraction/llms.txt BaseAccount provides execute and executeBatch functions callable by the EntryPoint. executeBatch reverts with ExecuteError on failure in multi-call batches. ```Solidity // Single call — transfer ETH to recipient account.execute(recipient, 1 ether, ""); ``` ```Solidity // Batch call — approve token then swap (atomically) BaseAccount.Call[] memory calls = new BaseAccount.Call[](2); calls[0] = BaseAccount.Call({ target: address(usdcToken), value: 0, data: abi.encodeCall(IERC20.approve, (address(swapRouter), type(uint256).max)) }); calls[1] = BaseAccount.Call({ target: address(swapRouter), value: 0, data: abi.encodeCall(ISwapRouter.exactInputSingle, (swapParams)) }); try account.executeBatch(calls) { // both calls succeeded } catch (bytes memory err) { // For multi-call batches: abi-decode ExecuteError(uint256 index, bytes error) (uint256 failIndex, bytes memory innerErr) = abi.decode(err[4:], (uint256, bytes)); } ``` -------------------------------- ### Implement Custom Paymaster with BasePaymaster Source: https://context7.com/eth-infinitism/account-abstraction/llms.txt Extend BasePaymaster to create custom paymasters. Implement _validatePaymasterUserOp for validation logic and optionally override _postOp for post-execution callbacks. Ensure the paymaster contract is funded. ```Solidity import "@account-abstraction/contracts/core/BasePaymaster.sol"; contract SponsorPaymaster is BasePaymaster { mapping(address => bool) public whitelisted; constructor(IEntryPoint _ep) BasePaymaster(_ep, msg.sender) {} function addToWhitelist(address account) external onlyOwner { whitelisted[account] = true; } // Called by EntryPoint during validation phase function _validatePaymasterUserOp( PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 maxCost ) internal override returns (bytes memory context, uint256 validationData) { require(whitelisted[userOp.sender], "not whitelisted"); // Return empty context (no postOp needed) and unlimited validity context = abi.encode(userOp.sender, maxCost); // pass data to postOp if needed validationData = _packValidationData(false, uint48(block.timestamp + 1 hours), 0); } // Called after the UserOperation executes (only when context is non-empty) function _postOp( PostOpMode mode, bytes calldata context, uint256 actualGasCost, uint256 actualUserOpFeePerGas ) internal override { (address sender, uint256 maxCost) = abi.decode(context, (address, uint256)); // e.g. charge sender an ERC-20 token proportional to actualGasCost emit GasSponsored(sender, actualGasCost, mode); } // Fund the paymaster deposit so it can pay for gas // paymaster.deposit{value: 1 ether}(); } ``` -------------------------------- ### Handle Batched User Operations with Aggregated Signatures Source: https://context7.com/eth-infinitism/account-abstraction/llms.txt Use `handleAggregatedOps` when accounts return an aggregator address from `validateUserOp`. This allows bundlers to combine signatures for gas savings by grouping operations with the same aggregator. ```Solidity import "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; IEntryPoint.UserOpsPerAggregator[] memory opsPerAgg = new IEntryPoint.UserOpsPerAggregator[](1); opsPerAgg[0] = IEntryPoint.UserOpsPerAggregator({ userOps: aggregatedOpsArray, // all ops that use the same aggregator aggregator: IAggregator(bls Aggregator), signature: combinedBLSSignature // one aggregated sig covering all ops in this group }); entryPoint.handleAggregatedOps(opsPerAgg, payable(bundlerAddress)); ``` -------------------------------- ### Implement Account Validation Logic Source: https://context7.com/eth-infinitism/account-abstraction/llms.txt The `validateUserOp` method is essential for ERC-4337 smart wallets. It validates the operation's signature and handles the gas prefund payment if the account owes funds to the EntryPoint. ```Solidity import "@account-abstraction/contracts/interfaces/IAccount.sol"; import "@account-abstraction/contracts/interfaces/PackedUserOperation.sol"; import "@account-abstraction/contracts/core/Helpers.sol"; // SIG_VALIDATION_FAILED, SIG_VALIDATION_SUCCESS contract MyAccount is IAccount { address public owner; IEntryPoint private immutable _entryPoint; function validateUserOp( PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds // ETH the account still owes to the EntryPoint ) external override returns (uint256 validationData) { require(msg.sender == address(_entryPoint), "only EntryPoint"); // 1. Validate signature if (ECDSA.recover(userOpHash, userOp.signature) != owner) return SIG_VALIDATION_FAILED; // 2. Pay prefund if needed if (missingAccountFunds > 0) { (bool ok,) = payable(msg.sender).call{value: missingAccountFunds}(""); (ok); // ignore result; EntryPoint checks balance itself } // Return packed (aggregator=0, validUntil=0=forever, validAfter=0) return SIG_VALIDATION_SUCCESS; } } ``` -------------------------------- ### Manage Gas Prefund Deposits Source: https://context7.com/eth-infinitism/account-abstraction/llms.txt Use `depositTo`, `balanceOf`, and `withdrawTo` to manage the ETH deposit pool for gas prefunding. Accounts and paymasters deposit ETH to cover operation costs, and unused gas is refunded. ```Solidity IEntryPoint entryPoint = IEntryPoint(0x4337084d9e255ff0702461cf8895ce9e3b5ff108); // Deposit ETH for an account so it can pay its own gas entryPoint.depositTo{value: 0.5 ether}(mySmartWallet); // Check current deposit balance uint256 balance = entryPoint.balanceOf(mySmartWallet); // => 500000000000000000 (0.5 ETH) // Withdraw deposit back (called from the account itself or via a UserOperation) entryPoint.withdrawTo(payable(recipient), 0.1 ether); ``` -------------------------------- ### Extend BaseAccount for Custom Accounts Source: https://context7.com/eth-infinitism/account-abstraction/llms.txt Subclass BaseAccount to create custom smart accounts. You only need to provide the entryPoint() and _validateSignature() methods. Ensure your owner address is correctly set. ```Solidity import "@account-abstraction/contracts/core/BaseAccount.sol"; import "@account-abstraction/contracts/core/Helpers.sol"; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; contract MyAccount is BaseAccount { address public owner; IEntryPoint private immutable _entryPoint; constructor(IEntryPoint ep, address _owner) { _entryPoint = ep; owner = _owner; } function entryPoint() public view override returns (IEntryPoint) { return _entryPoint; } // Only override signature logic; all other boilerplate is in BaseAccount function _validateSignature( PackedUserOperation calldata userOp, bytes32 userOpHash ) internal override returns (uint256 validationData) { bytes32 ethHash = MessageHashUtils.toEthSignedMessageHash(userOpHash); address signer = ECDSA.recover(ethHash, userOp.signature); return signer == owner ? SIG_VALIDATION_SUCCESS : SIG_VALIDATION_FAILED; } // Batch-execute multiple calls atomically via EntryPoint // entryPoint.handleOps will call: account.executeBatch([...calls]) // BaseAccount.executeBatch iterates and reverts on first failure, wrapping error with index } ``` -------------------------------- ### Pack and Unpack Validation Data with Helpers Source: https://context7.com/eth-infinitism/account-abstraction/llms.txt Utilizes helper functions to encode and decode validation data, including signature status, validity periods, and aggregator addresses. Use `_packValidationData` to create the `validationData` and `_parseValidationData` to inspect it. ```Solidity import "@account-abstraction/contracts/core/Helpers.sol"; // Pack: signature valid, operation valid from now until 1 hour from now uint256 validationData = _packValidationData( false, // sigFailed = false (signature OK) uint48(block.timestamp + 1 hours), // validUntil uint48(block.timestamp) // validAfter ); // => encodes as: (0) | (validUntil << 160) | (validAfter << 208) // Unpack for inspection ValidationData memory parsed = _parseValidationData(validationData); // parsed.aggregator == address(0) → valid signature, no aggregator // parsed.validUntil == block.timestamp + 3600 // parsed.validAfter == block.timestamp // Pack with an aggregator address (BLS / multi-sig scenarios) uint256 aggData = _packValidationData( ValidationData({ aggregator: address(blsAggregator), validAfter: 0, validUntil: 0 // 0 means "no expiry" → stored as type(uint48).max internally }) ); ``` -------------------------------- ### NonceManager Source: https://context7.com/eth-infinitism/account-abstraction/llms.txt Manages sequential nonces for smart accounts, allowing for parallel nonce sequences using a key. ```APIDOC ## NonceManager Provides sequential nonces per `(account, key)` pair. The `key` (high 192 bits) allows parallel nonce sequences, enabling out-of-order UserOps across different keys. ### `getNonce(address account, uint192 key)` Returns the next sequential nonce for the given account and key. ### `incrementNonce(uint192 key)` Manually pre-increments the nonce for the given key. This can be called directly from the account's EOA to initialize a nonce key, saving gas on the first UserOp. #### Example Usage: ```solidity IEntryPoint entryPoint = IEntryPoint(0x4337084d9e255ff0702461cf8895ce9e3b5ff108); // Get next sequential nonce for key=0 (standard usage) uint256 nonce = entryPoint.getNonce(mySmartWallet, 0); // Use a non-zero key for a parallel nonce lane (e.g., for session keys) uint192 sessionKey = 0x1; uint256 sessionNonce = entryPoint.getNonce(mySmartWallet, sessionKey); // Manually pre-increment a nonce key to initialize it (saves gas on first real UserOp) // Called directly from the account's EOA, not via a UserOperation entryPoint.incrementNonce(sessionKey); // Full nonce layout in PackedUserOperation.nonce: // nonce = (uint192(key) << 64) | uint64(sequence) ``` ``` -------------------------------- ### IAggregator Interface for Signature Aggregation Source: https://context7.com/eth-infinitism/account-abstraction/llms.txt This Solidity code demonstrates the usage of the IAggregator interface for BLS or other multi-signature aggregation schemes. It shows how to validate individual user operation signatures, aggregate them off-chain, and then validate the aggregated signature on-chain. ```Solidity import "@account-abstraction/contracts/interfaces/IAggregator.sol"; IAggregator aggregator = IAggregator(blsAggregatorAddress); // Off-chain: validate each individual op's signature and get its contribution bytes memory sigForUserOp = aggregator.validateUserOpSignature(userOp); // Off-chain: combine all per-op contributions into one aggregated signature PackedUserOperation[] memory ops = [op1, op2, op3]; bytes memory aggregatedSig = aggregator.aggregateSignatures(ops); // On-chain (called inside EntryPoint): verify the combined signature aggregator.validateSignatures(ops, aggregatedSig); // Reverts if the aggregated signature does not cover all ops ``` -------------------------------- ### Combine EIP-7702 and ERC-4337 with Simple7702Account Source: https://context7.com/eth-infinitism/account-abstraction/llms.txt This contract enables an EOA to use ERC-4337 for gas sponsoring and batch execution by setting its code via EIP-7702. The EOA's private key signs the UserOp. ```solidity import "@account-abstraction/contracts/accounts/Simple7702Account.sol"; // Deploy once; all EIP-7702 accounts share this implementation Simple7702Account impl = new Simple7702Account(entryPoint); // EIP-7702: the EOA signs an authorization to set its code to impl's bytecode. // After the authorization is applied on-chain, set initCode in the UserOp: bytes memory initCode = abi.encodePacked( bytes2(0x7702), // EIP-7702 marker // optionally followed by initialization payload ); PackedUserOperation memory userOp = PackedUserOperation({ sender: eoaAddress, // the EOA whose code is now delegated nonce: entryPoint.getNonce(eoaAddress, 0), initCode: initCode, // triggers EIP7702AccountInitialized event callData: abi.encodeCall(Simple7702Account.executeBatch, (calls)), // ... gas fields, signature }); // Simple7702Account validates using: ECDSA.recover(hash, sig) == address(this) // i.e., the EOA's own private key signs the userOpHash bool valid = simple7702Account.isValidSignature(msgHash, signature) == IERC1271.isValidSignature.selector; ``` -------------------------------- ### Simple7702Account Source: https://context7.com/eth-infinitism/account-abstraction/llms.txt Combines EIP-7702 and ERC-4337 for EOA-based smart accounts without deploying separate proxies. ```APIDOC ## Simple7702Account A minimal account for EIP-7702 delegate-code authorization. An EOA sets its code to this contract's bytecode via an EIP-7702 authorization, then can use ERC-4337 for gas sponsoring and batch execution without deploying a separate proxy. ### `executeBatch(address to, bytes[] calls)` Executes a batch of calls. ### `isValidSignature(bytes32 hash, bytes signature)` Validates a signature using the EOA's private key. #### Example Usage: ```solidity import "@account-abstraction/contracts/accounts/Simple7702Account.sol"; // Deploy once; all EIP-7702 accounts share this implementation Simple7702Account impl = new Simple7702Account(entryPoint); // EIP-7702: the EOA signs an authorization to set its code to impl's bytecode. // After the authorization is applied on-chain, set initCode in the UserOp: bytes memory initCode = abi.encodePacked( bytes2(0x7702), // EIP-7702 marker // optionally followed by initialization payload ); PackedUserOperation memory userOp = PackedUserOperation({ sender: eoaAddress, // the EOA whose code is now delegated nonce: entryPoint.getNonce(eoaAddress, 0), initCode: initCode, // triggers EIP7702AccountInitialized event callData: abi.encodeCall(Simple7702Account.executeBatch, (calls)), // ... gas fields, signature }); // Simple7702Account validates using: ECDSA.recover(hash, sig) == address(this) // i.e., the EOA's own private key signs the userOpHash bool valid = simple7702Account.isValidSignature(msgHash, signature) == IERC1271.isValidSignature.selector; ``` ``` -------------------------------- ### IAccount - validateUserOp Source: https://context7.com/eth-infinitism/account-abstraction/llms.txt The single method every ERC-4337 smart wallet must implement. The EntryPoint calls it during validation to authenticate the operation and collect a gas prefund. ```APIDOC ## validateUserOp ### Description The single method every ERC-4337 smart wallet must implement. The EntryPoint calls it during validation to authenticate the operation and collect a gas prefund. ### Method `validateUserOp` ### Parameters - `userOp` (PackedUserOperation calldata) - The user operation to validate. - `userOpHash` (bytes32) - The hash of the user operation. - `missingAccountFunds` (uint256) - The amount of ETH the account still owes to the EntryPoint. ### Returns - `validationData` (uint256) - Returns `SIG_VALIDATION_SUCCESS` (0) if validation passes, or `SIG_VALIDATION_FAILED` (1) otherwise. Can also return other data related to signature validation, like validity bounds. ### Implementation Details ```solidity import "@account-abstraction/contracts/interfaces/IAccount.sol"; import "@account-abstraction/contracts/interfaces/PackedUserOperation.sol"; import "@account-abstraction/contracts/core/Helpers.sol"; // SIG_VALIDATION_FAILED, SIG_VALIDATION_SUCCESS contract MyAccount is IAccount { address public owner; IEntryPoint private immutable _entryPoint; function validateUserOp( PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds // ETH the account still owes to the EntryPoint ) external override returns (uint256 validationData) { require(msg.sender == address(_entryPoint), "only EntryPoint"); // 1. Validate signature if (ECDSA.recover(userOpHash, userOp.signature) != owner) return SIG_VALIDATION_FAILED; // 2. Pay prefund if needed if (missingAccountFunds > 0) { (bool ok,) = payable(msg.sender).call{value: missingAccountFunds}(""); (ok); // ignore result; EntryPoint checks balance itself } // Return packed (aggregator=0, validUntil=0=forever, validAfter=0) return SIG_VALIDATION_SUCCESS; } } ``` ``` -------------------------------- ### Compute User Operation Hash for Signing Source: https://context7.com/eth-infinitism/account-abstraction/llms.txt Use `getUserOpHash` to compute the canonical hash of a `UserOperation` that accounts must sign. This hash includes the EntryPoint address and chain ID, excluding the signature field itself. ```Solidity import "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; IEntryPoint entryPoint = IEntryPoint(0x4337084d9e255ff0702461cf8895ce9e3b5ff108); bytes32 userOpHash = entryPoint.getUserOpHash(userOp); // Off-chain: sign userOpHash with the owner's private key (ethers.js example) // const sig = await owner.signMessage(ethers.utils.arrayify(userOpHash)); // userOp.signature = sig; // On-chain: verify inside _validateSignature function _validateSignature( PackedUserOperation calldata userOp, bytes32 userOpHash ) internal override returns (uint256 validationData) { address recovered = ECDSA.recover(userOpHash, userOp.signature); if (recovered != owner) return SIG_VALIDATION_FAILED; // = 1 return SIG_VALIDATION_SUCCESS; // = 0 } ``` -------------------------------- ### StakeManager: Staking for Paymasters and Factories Source: https://context7.com/eth-infinitism/account-abstraction/llms.txt Manages staking for paymasters and factories, requiring a locked stake with a mandatory withdrawal delay. Use `addStake` to deposit ETH, `unlockStake` to initiate withdrawal, and `withdrawStake` to claim after the delay. ```Solidity IEntryPoint entryPoint = IEntryPoint(0x4337084d9e255ff0702461cf8895ce9e3b5ff108); // Add stake (1 ETH, 1-day unlock delay) — called by the paymaster or factory contract entryPoint.addStake{value: 1 ether}(uint32(1 days)); // Inspect stake and deposit state IStakeManager.DepositInfo memory info = entryPoint.getDepositInfo(address(myPaymaster)); // info.deposit — ETH available for gas payments // info.staked — true if currently staked // info.stake — locked ETH amount // info.unstakeDelaySec — required delay before withdrawal // info.withdrawTime — timestamp when withdrawal becomes available (0 if still locked) // Begin unlock (starts the timer) entryPoint.unlockStake(); // After unstakeDelaySec seconds have passed, withdraw the stake entryPoint.withdrawStake(payable(recipientAddress)); ``` -------------------------------- ### Implement Custom Paymaster Logic Source: https://github.com/eth-infinitism/account-abstraction/blob/develop/README.md Extend the BasePaymaster contract to implement custom gas payment logic for your paymaster. The _validatePaymasterUserOp function should contain your specific validation and context setting. ```solidity import "@account-abstraction/contracts/core/BasePaymaster.sol"; contract MyCustomPaymaster is BasePaymaster { /// implement your gas payment logic here function _validatePaymasterUserOp( PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 maxCost ) internal virtual override returns (bytes memory context, uint256 validationData) { context = “”; // specify “context” if needed in postOp call. validationData = _packValidationData( false, validUntil, validAfter ); } } ``` -------------------------------- ### Implement Custom Smart Contract Account Logic Source: https://github.com/eth-infinitism/account-abstraction/blob/develop/README.md Extend the BaseAccount contract to implement custom authentication logic for your smart contract wallet. The _validateSignature function should contain your specific signature verification process. ```solidity import "@account-abstraction/contracts/core/BaseAccount.sol"; contract MyAccount is BaseAccount { /// implement your authentication logic here function _validateSignature(PackedUserOperation calldata userOp, bytes32 userOpHash) internal override virtual returns (uint256 validationData) { // UserOpHash can be generated using eth_signTypedData_v4 if (owner != ECDSA.recover(userOpHash, userOp.signature)) return SIG_VALIDATION_FAILED; return SIG_VALIDATION_SUCCESS; } } ``` -------------------------------- ### Define PackedUserOperation struct Source: https://context7.com/eth-infinitism/account-abstraction/llms.txt Defines the fundamental data type for user operations passed to the EntryPoint. Ensure all fields are correctly populated, especially the packed gas limits and fees. ```solidity import "@account-abstraction/contracts/interfaces/PackedUserOperation.sol"; // Field layout reference: // accountGasLimits = uint128(verificationGasLimit) || uint128(callGasLimit) // gasFees = uint128(maxPriorityFeePerGas) || uint128(maxFeePerGas) // paymasterAndData = paymaster(20) || verificationGasLimit(16) || postOpGasLimit(16) || paymasterData // nonce = uint192(key) || uint64(sequence) PackedUserOperation memory userOp = PackedUserOperation({ sender: 0xYourSmartWalletAddress, nonce: entryPoint.getNonce(walletAddress, 0), // key=0, next sequential nonce initCode: bytes(""), // empty if wallet already deployed callData: abi.encodeCall(ITarget.doSomething, (arg1)), // encoded call to execute accountGasLimits: bytes32( (uint256(150_000) << 128) | uint256(100_000) // verifyGas=150k, callGas=100k ), preVerificationGas: 50_000, gasFees: bytes32( (uint256(1e9) << 128) | uint256(20e9) // maxPriorityFee=1gwei, maxFee=20gwei ), paymasterAndData: bytes(""), // no paymaster signature: ownerSignature // ECDSA sig over userOpHash }); ``` -------------------------------- ### EntryPoint - getSenderAddress Source: https://context7.com/eth-infinitism/account-abstraction/llms.txt Computes the counterfactual address of a not-yet-deployed account from its initCode. This method always reverts with SenderAddressResult, which must be caught off-chain to retrieve the address. ```APIDOC ## getSenderAddress ### Description Computes the counterfactual address of a not-yet-deployed account from its `initCode`. Always reverts with `SenderAddressResult(address)` — catch it off-chain to read the address. ### Method `getSenderAddress` ### Parameters - `initCode` (bytes) - The code to deploy the account, typically consisting of the factory address and an encoded `createAccount` call. ### Response - `SenderAddressResult(address)` - Emitted upon successful computation, containing the predicted sender address. This is returned via a revert. ### Request Example ```solidity // Construct initCode: factory address (20 bytes) + abi-encoded createAccount call bytes memory initCode = abi.encodePacked( address(factory), abi.encodeCall(SimpleAccountFactory.createAccount, (ownerAddress, salt)) ); // This always reverts; catch the error to extract the sender address try entryPoint.getSenderAddress(initCode) { revert("getSenderAddress: should have reverted"); } catch (bytes memory reason) { // reason is abi-encoded SenderAddressResult(address) address sender = abi.decode(reason[4:], (address)); // Use sender as userOp.sender before the account is deployed } ``` ``` -------------------------------- ### Encode Paymaster and Data for UserOperation Source: https://context7.com/eth-infinitism/account-abstraction/llms.txt Encodes the paymaster address, verification gas limits, post-operation gas limits, and custom data for a UserOperation. Ensure the encoded data matches the expected format for the EntryPoint. ```Solidity import "@account-abstraction/contracts/interfaces/IPaymaster.sol"; // paymasterAndData encoding (set in the UserOperation): // bytes20(paymasterAddress) || bytes16(verificationGasLimit) || bytes16(postOpGasLimit) || customData bytes memory paymasterAndData = abi.encodePacked( address(myPaymaster), // 20 bytes uint128(80000), // paymaster verificationGasLimit (16 bytes) uint128(40000), // paymaster postOpGasLimit (16 bytes) abi.encode(expiry, sig) // custom paymaster data / signature ); // PostOpMode values: // IPaymaster.PostOpMode.opSucceeded — user op executed successfully // IPaymaster.PostOpMode.opReverted — user op reverted; paymaster still pays gas // IPaymaster.PostOpMode.postOpReverted — internal only; never sent to paymaster ``` -------------------------------- ### Compute Counterfactual Sender Address Source: https://context7.com/eth-infinitism/account-abstraction/llms.txt Use `getSenderAddress` to compute the address of a future account based on its factory and init code. This function always reverts, so catch the exception to extract the sender address off-chain. ```Solidity // Construct initCode: factory address (20 bytes) + abi-encoded createAccount call bytes memory initCode = abi.encodePacked( address(factory), abi.encodeCall(SimpleAccountFactory.createAccount, (ownerAddress, salt)) ); // This always reverts; catch the error to extract the sender address try entryPoint.getSenderAddress(initCode) { revert("getSenderAddress: should have reverted"); } catch (bytes memory reason) { // reason is abi-encoded SenderAddressResult(address) address sender = abi.decode(reason[4:], (address)); // Use sender as userOp.sender before the account is deployed } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.