on Robinhood Chain · testnet

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.
EntropyUnavailable()
No L2 entropy for the seed on a real chain (the roll reverts rather than use a predictable seed).
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.
OwnableUnauthorizedAccount(address account)
Ownable: caller is not the 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.