// SPDX-License-Identifier: MIT pragma solidity ^0.8.23; import "@openzeppelin/contracts/access/Ownable.sol"; /// @dev Minimal subset of ERC-4337 v0.7 IEntryPoint needed by paymasters. interface IEntryPoint { function depositTo(address account) external payable; function balanceOf(address account) external view returns (uint256); function withdrawTo(address payable withdrawAddress, uint256 amount) external; function addStake(uint32 unstakeDelaySec) external payable; function unlockStake() external; function withdrawStake(address payable withdrawAddress) external; } /// @dev ERC-4337 v0.7 PackedUserOperation struct (used by paymaster hook). struct PackedUserOperation { address sender; uint256 nonce; bytes initCode; bytes callData; bytes32 accountGasLimits; uint256 preVerificationGas; bytes32 gasFees; bytes paymasterAndData; bytes signature; } /** * @title PaymasterBase * @notice Minimal in-house BasePaymaster compatible with OpenZeppelin v4.9. * Avoids the OZ-v5 dependency that the upstream eth-infinitism package pulls in. * * ERC-4337 v0.7 spec: * - validatePaymasterUserOp returns (context, validationData) * - postOp is called with (mode, context, actualGasCost, actualUserOpFeePerGas) * * Subclasses override `_validatePaymasterUserOp` and (optionally) `_postOp`. */ abstract contract PaymasterBase is Ownable { IEntryPoint public immutable entryPoint; enum PostOpMode { opSucceeded, opReverted, postOpReverted } modifier onlyEntryPoint() { require(msg.sender == address(entryPoint), "Paymaster: not from EntryPoint"); _; } constructor(IEntryPoint _entryPoint) { entryPoint = _entryPoint; } /// EntryPoint calls this during UserOp validation. function validatePaymasterUserOp( PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 maxCost ) external onlyEntryPoint returns (bytes memory context, uint256 validationData) { return _validatePaymasterUserOp(userOp, userOpHash, maxCost); } /// EntryPoint calls this after UserOp execution (if the paymaster requested it via context). function postOp( PostOpMode mode, bytes calldata context, uint256 actualGasCost, uint256 actualUserOpFeePerGas ) external onlyEntryPoint { _postOp(mode, context, actualGasCost, actualUserOpFeePerGas); } function _validatePaymasterUserOp( PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 maxCost ) internal virtual returns (bytes memory context, uint256 validationData); function _postOp( PostOpMode /*mode*/, bytes calldata /*context*/, uint256 /*actualGasCost*/, uint256 /*actualUserOpFeePerGas*/ ) internal virtual { // Default: no-op. Most paymasters don't need post-op logic. } // ───────────────────── EntryPoint deposit helpers ───────────────────── /// Fund the paymaster's gas deposit at the EntryPoint. function deposit() external payable { entryPoint.depositTo{ value: msg.value }(address(this)); } function depositBalance() external view returns (uint256) { return entryPoint.balanceOf(address(this)); } function withdrawDeposit(address payable to, uint256 amount) external onlyOwner { entryPoint.withdrawTo(to, amount); } function addStake(uint32 unstakeDelaySec) external payable onlyOwner { entryPoint.addStake{ value: msg.value }(unstakeDelaySec); } function unlockStake() external onlyOwner { entryPoint.unlockStake(); } function withdrawStake(address payable to) external onlyOwner { entryPoint.withdrawStake(to); } receive() external payable {} }