Docs · 03 / 07 · Contract reference
PowPaymaster
An ERC-4337 paymaster for EntryPoint v0.9, written in Solidity 0.8.28. It implements IPaymaster directly, is
staked, is not upgradeable, and keeps value inside the system: ETH leaves only through withdrawTreasury.
Not deployed on Robinhood Chain testnet yet. The address is deterministic (CREATE2 with salt pow-paymaster/v1.1) and appears here after the deployment.
Signatures below are generated from the ABI the SDK ships, so they always match the deployed build. Struct layouts are in
contracts/src/interfaces/IPowPaymaster.sol.
Lifecycle and admin#
acceptOwnership()pending owner- Completes the ownership transfer.
addStake(uint32 unstakeDelaySec) payableowner- Stakes the paymaster in the EntryPoint (required by ERC-7562 to read its own storage in validation).
deposit() payableanyone- Adds msg.value to this paymaster's EntryPoint deposit.
drainDeposit()owner or guardian- Withdraws the whole EntryPoint deposit into the contract (the treasury) and pauses. Kill switch.
fund(bytes32 source) payableanyone- Treasury income from anyone, tagged with a free-form source (for example a fee router). Emits Funded.
pause()owner or guardian- Blocks roll() for everyone. Validation never reads the flag, so live epochs keep working until they expire.
postOp(PostOpMode mode, bytes context, uint256 actualGasCost, uint256 actualUserOpFeePerGas)EntryPoint- EntryPoint hook. Accounting only: adds the op's cost and work to the current epoch's accumulator and emits OpSponsored.
renounceOwnership()owner- Leaves the contract without an owner.
roll() returns (uint32 newEpochId)EOA or owner- Closes the current epoch and opens the next. Any EOA once the epoch is over, or the owner at any time. Tops the deposit up to the budget only on a direct EOA call, at most once per epoch length.
setConfig(Config cfg)owner- Stores a new pending config; it applies at the next roll. Checked against the config rules (§3.13).
setGuardian(address guardian_)owner- Sets the guardian, which can pause and drain but never move value out.
topUpDeposit()owner, once- Initial funding of the deposit up to the configured budget. Callable once.
transferOwnership(address newOwner)owner- Starts a two-step ownership transfer (Ownable2Step).
trimDeposit(uint256 amount)owner, paused- Withdraws part of the deposit into the treasury, only while paused.
unlockStake()owner- Starts the unstake delay.
unpause()owner- Clears the pause.
validatePaymasterUserOp(PackedUserOperation userOp, bytes32 userOpHash, uint256 maxCost) view returns (bytes context, uint256 validationData)EntryPoint- EntryPoint hook. Reverts only on malformed data or an unknown epoch; every policy, PoW and co-signature failure is soft (SIG_VALIDATION_FAILED).
withdrawStake()owner- Withdraws the unlocked stake, always to this contract.
withdrawTreasury(address to, uint256 amount)owner- Sends treasury ETH. The only path by which value leaves the system (timelocked on mainnet).
Views#
DELEGATE() view returns (address)- The only delegate a sender may use (Simple7702Account v0.9).
MAX_DAILY_SPEND() view returns (uint256)- Immutable ceiling on daily outflow from the deposit.
MAX_POSTOP_GAS_LIMIT() view returns (uint256)- Upper bound for pmPostOpGasLimit (the EntryPoint's unused-postOp-gas penalty threshold).
MAX_WORK() view returns (uint256)- Bound on the expected total hashes of any op (2^48).
MIN_POSTOP_GAS_LIMIT() view returns (uint256)- Lower bound for pmPostOpGasLimit.
PM_DATA_VERSION() view returns (uint8)- The paymasterData version byte (1).
POSTOP_GAS_OVERHEAD() view returns (uint256)- Gas added to each op's accounted cost for postOp and the EntryPoint's bookkeeping.
POW_SOLUTIONS() view returns (uint256)- K = 8 sub-solutions per proof.
accumulator() view returns (Accumulator)- Spend counters of the current epoch.
checkUserOp(PackedUserOperation op, bytes32 userOpHash, uint256 maxCost) view returns (uint256 failMask, uint48 validUntil, uint256 work)- Mirrors validation exactly, including the delegate check, and returns the failMask, validUntil and work.
config() view returns (Config)- The pending configuration the next roll snapshots.
controller() view returns (Controller)- Difficulty controller state and the budget in force.
cosignDigest(bytes32 userOpHash) view returns (bytes32)- The digest the relay co-signer signs (raw, no EIP-191 prefix).
currentEpochId() view returns (uint32)- The wall-clock current epoch id.
dashboard() view returns (uint32 currentId, Epoch current, Accumulator acc, Controller ctl, Totals tot, uint256 depositWei, uint256 treasuryWei, uint256 stakeWei, uint32 unstakeDelaySec, bool staked)- One-call snapshot for dashboards and the relay: current epoch, accumulator, controller, totals, deposit, treasury and stake.
entryPoint() view returns (address)- The EntryPoint v0.9 address.
epochStats(uint32 epochId) view returns (EpochStats)- A closed epoch's ops, spend, work, utilization and close time.
epochStatsRange(uint32 fromId, uint32 toId) view returns (EpochStats[] out)- Stats for epochs fromId..toId inclusive, at most 256 entries.
epochs(uint32 epochId) view returns (Epoch)- An epoch's write-once parameters (start 0 = unknown epoch). The only storage validation reads.
getDeposit() view returns (uint256)- This paymaster's EntryPoint deposit.
guardian() view returns (address)- The guardian address.
owner() view returns (address)- The owner (a timelock on mainnet).
pendingOwner() view returns (address)- The pending owner of a two-step transfer.
powChallenge(uint32 epochId, bytes32 userOpHash) view returns (bytes32)- The PoW challenge for a userOpHash in an epoch.
requiredWork(uint32 epochId, uint256 gasSum) view returns (uint256 work, uint256 targetPerSolution)- Expected total hashes and the per-solution target for an op of gasSum in an epoch. Bit-exact with the SDK.
totals() view returns (Totals)- Lifetime ops and spend over closed epochs.
Events#
The EntryPoint also emits UserOperationEvent(userOpHash, sender, paymaster, nonce, success, actualGasCost, actualGasUsed).
Join it with OpSponsored by transaction hash and sender.
ConfigUpdated(Config config)- A new pending config was stored.
DepositDrained(uint256 amount, address indexed by)- The deposit was drained into the treasury.
DepositToppedUp(uint256 before, uint256 after_)- A roll topped the deposit up.
DepositTrimmed(uint256 amount)- Part of the deposit was moved back into the treasury.
EpochClosed(uint32 indexed epochId, uint32 ops, uint96 spent, uint96 work, uint16 utilBps)- The previous epoch's final ops, spend, work and utilization.
EpochRolled(uint32 indexed epochId, uint40 start, uint64 workPerGwei, uint64 minWork, uint64 maxFeePerGas, uint64 refFeePerGas, uint96 seed, uint8 flags, address cosigner, address indexed caller)- A new epoch exists: its difficulty, fees, seed, flags and co-signer.
Funded(address indexed from, uint256 amount, bytes32 indexed source)- Treasury income through fund().
GuardianSet(address indexed guardian)- A new guardian.
OpSponsored(uint32 indexed epochId, uint32 opEpochId, address indexed sender, uint256 cost, uint256 work, bool success)- One sponsored op, accounted in postOp. The dashboard joins it with UserOperationEvent by transaction hash.
OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner)- A two-step ownership transfer started.
OwnershipTransferred(address indexed previousOwner, address indexed newOwner)- Ownership changed.
PausedSet(bool paused, address indexed by)- roll() was paused or unpaused.
TreasuryLow(uint256 needed, uint256 available)- A top-up could not reach the budget: the treasury held less than needed.
TreasuryWithdrawn(address indexed to, uint256 amount)- Treasury ETH left the system.
Errors#
Reverts happen only on malformed input, an unknown epoch or an admin misuse. Policy, proof and co-signature problems are
soft failures, reported as bits by checkUserOp() (see How it works).
ERC165Error(address entryPoint, bytes4 interfaceId)- The constructor's EntryPoint does not support the v0.9 interface.
EpochNotOver(uint256 endsAt)- A non-owner roll before the epoch's end.
InvalidConfig(uint8 code)- A config rule failed; the code is the rule number (§3.13).
InvalidConstructorArg(uint8 code)- 1: the delegate has no code. 2: maxDailySpend is 0 or above the limit.
InvalidPaymasterData(uint256 length)- The signed paymasterData is not exactly 5 bytes (version ‖ epochId).
InvalidPaymasterSignatureLength(uint256 dataLength, uint256 pmSignatureLength)- The EntryPoint's paymasterSignature length check failed.
InvalidPowSignatureLength(uint256 got, uint256 expected)- The unsigned suffix is not 64 (permissionless) or 129 (co-signed) bytes.
InvalidRange(uint32 fromId, uint32 toId)- epochStatsRange with toId < fromId or more than 256 entries.
NotFromEntryPoint(address msgSender, address entity, address entryPoint)- A paymaster hook called by something other than the EntryPoint.
NotOwnerOrGuardian()- pause() or drainDeposit() from anyone else.
NotPaused()- trimDeposit() while not paused.
OwnableInvalidOwner(address owner)- Ownable: invalid new owner.
RollCallerNotEOA()- roll() from a contract that is not the owner (this blocks rolls inside handleOps).
RollPaused()- roll() while paused.
TopUpAlreadyDone()- topUpDeposit() called twice.
TransferFailed()- An ETH transfer failed.
UnknownEpoch(uint32 epochId)- The op references an epoch that does not exist.
UnsupportedVersion(uint8 version)- The paymasterData version is not 1.