STAMP

DocsHow it works

A stamp replaces the funding transaction.

A fresh address can't move until something pays its gas, and funding it from your main wallet links the two on a public ledger. The Stamp paymaster pays instead, for any UserOperation that carries a stamp: a prepaid stamp bought in advance and blind-signed by the relay, or a stamp mined on the spot with proof of work. It asks the fresh address for no ETH and no signature from a known account.

Two postages#

Both postages go through the same relay, the same epochs and the same policy checks. They differ in who pays and what rations them.

Two postages, one paymaster
PostagePrepaid stampMined stamp (free)
What the client bringsOne blind-signed stamp from its saved stamps8 keccak sub-solutions bound to the op's hash
Who pays the gasThe buyer, in advance: the stamp float in the paymaster's depositThe free lane: each epoch's budget, refilled from the treasury
What rations itThe deposit's headroom onlyDifficulty, the epoch budget and the relay's pacing
paymasterData version2 (no work; nullifier ‖ co-signature)1 (nonces ‖ co-signature)
Relay methodstamp_sendUserOperationeth_sendUserOperation
Exact postOp gas limitstampPostOpGasLimitpmPostOpGasLimit
Accounted asStampCancelled (the nullifier recorded)OpSponsored (spend and work of the epoch)
Cost to youThe stamp's price in ETH, paid once from any walletSeconds of CPU; nothing in ETH

A prepaid stamp is e-cash for gas: bought from any wallet, it carries no trace of that wallet when it is used. Prepaid stamps covers its whole life; this page covers what the two postages share and the proof of work.

  1. BuyAny wallet pays PostOffice.buy for N blinded serials. The purchase is public and commits to the blinded messages.PostOffice.buy
  2. IssueThe relay checks the mined purchase and blind-signs exactly the committed messages. It never sees a serial.stamp_issue
  3. KeepThe client unblinds the signatures into stamps and keeps them, encrypted when exported. A stamp is a bearer token.local only
  4. UseA fresh address with 0 ETH sends one operation with one stamp. The relay verifies it and co-signs the op with its nullifier.stamp_sendUserOperation
  5. CancelThe paymaster pays the gas from the stamp float and records the nullifier in postOp. A second use is public.StampCancelled

The relay meets a stamp twice, blinded at issuance and unblinded at use, and cannot match the two. What it can match is your IP address and your timing: see the privacy guide.

One stamp, five steps. Only the purchase and the cancellation are on chain; the serial and the signature never are.

The flow#

  1. Epoch. The SDK polls the relay's pow_getEpoch every 60 s in the background. An epoch (15 minutes) fixes the fee caps, gas caps, the difficulty and a random seed revealed only when the epoch starts, and whether prepaid stamps are accepted (the STAMPS flag).
  2. Build. The op's gas limits come from fixed per-template profiles, its fees from the epoch, its pre-verification gas from the relay's quote. The EntryPoint nonce key is always 0. A first op carries an EIP-7702 authorization for Simple7702Account v0.9.
  3. Hash. userOpHash is the EntryPoint v0.9 EIP-712 hash, which covers the 7702 delegation.
  4. Attach the stamp. With a prepaid stamp, the op carries the stamp's nullifier, and the account signs at once. With a mined stamp, the proof lives in the unsigned paymasterSignature suffix, so the account signs while the miner searches for 8 nonces.
  5. Relay. The relay recomputes the hash, verifies the stamp (its signature, and that it was never spent) or the proof, and every on-chain rule, reserves the op's maximum cost against the deposit, co-signs and forwards the op to its own bundler (or holds it for the delay you asked for).
  6. Bundle. The bundler sends a type-4 transaction that applies the delegation and calls handleOps. The fresh address holds 0 ETH before and after.
  7. Validate and account. validatePaymasterUserOp checks the co-signature, the policy and, for a mined stamp, the proof, against the epoch's write-once parameters. postOp records the cost: StampCancelled (the nullifier recorded, the cost taken from the stamp float) or OpSponsored (the epoch's spend).

paymasterAndData#

The version byte of the signed paymasterData selects the postage. Everything the relay adds sits in the EntryPoint v0.9 paymasterSignature suffix, which userOpHash excludes: the signed part only carries the version and the epoch id.

Prepaid stamp · version 2 · 164 bytes

  • In userOpHash, signed by the account
  • The stamp's nullifier, keccak256(abi.encode(NULLIFIER_TAG, serial))
  • Relay co-signature over userOpHash and the nullifier, and the length
A prepaid stamp's paymasterAndData, drawn to scale (164 bytes). No work: the suffix holds the nullifier the paymaster records in postOp, and the relay's co-signature, which only the relay can produce once it has checked the stamp. The stamp's serial and signature go to the relay alone, never on chain.
Prepaid stamp: paymasterAndData (version 2, 164 bytes)
OffsetBytesFieldEncodingIn userOpHash
020paymasteraddressyes
2016paymasterVerificationGasLimituint128 BE, must equal the epoch's pmVerificationGasLimityes
3616paymasterPostOpGasLimituint128 BE, must equal the epoch's stampPostOpGasLimityes
521version = 0x02uint8yes (signed paymasterData)
534epochIduint32 BE; the epoch must have the STAMPS flagyes (signed paymasterData)
5732nullifierkeccak256(abi.encode(NULLIFIER_TAG, serial)), never 0no (paymasterSignature)
8965cosigr ‖ s ‖ v over stampDigest(userOpHash, nullifier), low-sno (paymasterSignature)
1542sigLenuint16 BE: 0x0061 (97)no
1568PAYMASTER_SIG_MAGIC0x22e325a297439656yes (the magic only)

Mined stamp · version 1 · 196 bytes

  • In userOpHash, signed by the account
  • The 8 proof-of-work nonces (8 B each)
  • Relay co-signature and length
A mined stamp's paymasterAndData in co-signed mode, drawn to scale (196 bytes). The proof lives in the unsigned suffix, so the account can sign while the miner searches.
Mined stamp: paymasterAndData (version 1, co-signed, 196 bytes)
OffsetBytesFieldEncodingIn userOpHash
020paymasteraddressyes
2016paymasterVerificationGasLimituint128 BE, must equal the epoch's pmVerificationGasLimityes
3616paymasterPostOpGasLimituint128 BE, must equal the epoch's pmPostOpGasLimityes
521version = 0x01uint8yes (signed paymasterData)
534epochIduint32 BEyes (signed paymasterData)
5764nonces[0..7]8 × uint64 BE, strictly increasingno (paymasterSignature)
12165cosigr ‖ s ‖ v over cosignDigest(userOpHash), v ∈ {27, 28}, low-sno (paymasterSignature)
1862sigLenuint16 BE: 0x0081 (129)no
1888PAYMASTER_SIG_MAGIC0x22e325a297439656yes (the magic only)

In viem terms: paymasterData = version ‖ epochId (5 bytes), and paymasterSignature = nullifier ‖ cosig (97 bytes) for a prepaid stamp or nonces ‖ cosig (129 bytes) for a mined one. viem appends the length and the magic itself. Before the relay co-signs, the SDK fills the co-signature with a 65-byte stub (and, mining, the nonces with 64 zero bytes), so the hashed length is already final.

The challenge (mined stamp)#

Solidity
challenge = keccak256(abi.encode(
    CHALLENGE_TAG,        // keccak256("StampPaymaster/v1/challenge")
    block.chainid,
    address(entryPoint),
    address(this),        // the paymaster
    epochId,              // uint32
    seed,                 // uint96, revealed when the epoch starts
    userOpHash            // EntryPoint v0.9 EIP-712 hash
))
h_i = keccak256(abi.encode(challenge, uint256(nonce_i)))   // one 64-byte keccak block
accept_i  ⇔  uint256(h_i) ≤ targetPerSolution
  • Bound to one op. The chain id, EntryPoint and paymaster are in the challenge, and the EntryPoint nonce inside userOpHash makes each proof usable once.
  • No stockpiling. The seed is unknown until the epoch starts, ops expire 5 minutes after the next roll, and the relay refuses an old epoch's ops 2 minutes after a roll.
  • 8 independent solutions. The 8 nonces must be strictly increasing and each must hash under the target.
  1. Inputs

    CHALLENGE_TAG
    0x653a…82cc
    chainid
    4663
    entryPoint
    0x4337…D009
    paymaster
    0x7e57…c0de
    epochId
    145
    seed
    0x5eed…5eed
    userOpHash
    0xfc83…76b7
  2. challenge = keccak256(abi.encode(…))

    0x65fcac027d28e65abf25c937a24111f91518accdad0f9427ca637c895a5b727f

  3. h = keccak256(challenge ‖ nonce) ≤ target12 zero bits here

    • Sub-solution 1: nonce 2,723, hash 0x0001d98f…330253a9, 15 leading zero bits.
    • Sub-solution 2: nonce 5,024, hash 0x000eecd8…ccc3bcbe, 12 leading zero bits.
    • Sub-solution 3: nonce 5,566, hash 0x0004e561…557eb662, 13 leading zero bits.
    • Sub-solution 4: nonce 9,304, hash 0x0003e89a…2227d416, 14 leading zero bits.
    • Sub-solution 5: nonce 12,669, hash 0x00022bd8…72c45183, 14 leading zero bits.
    • Sub-solution 6: nonce 13,425, hash 0x0007f014…4ae3f26e, 13 leading zero bits.
    • Sub-solution 7: nonce 18,494, hash 0x000ee08f…863c943d, 12 leading zero bits.
    • Sub-solution 8: nonce 21,233, hash 0x000889e9…458e8222, 12 leading zero bits.
A worked proof with real values (example inputs, not a deployment): the challenge binds the chain, the EntryPoint, the paymaster, the epoch's seed and the op, then 8 strictly increasing nonces each hash under the target. The bright digits are the leading zeros that make a hash valid. Real ops need about 22 zero bits each.

How much work (mined stamp)#

text
gasSum      = verificationGasLimit + callGasLimit + paymasterVerificationGasLimit
              + paymasterPostOpGasLimit + preVerificationGas
work        = max(minWork, gasSum × refFeePerGas × workPerGwei / 1e9)   // expected total hashes
perSolution = ceil(work / 8)
target      = (2^256 − 1) / perSolution
  • Priced like the gas the paymaster pays. refFeePerGas is the base fee snapshotted at the roll, so work follows fee levels, and fee headroom in maxFeePerGas costs no extra work.
  • Tight limits pay off. Every client of a template uses the same published gas profile, so the work is the same for everyone.
  • K = 8. With 8 sub-solutions the 95th-percentile solve time is about 1.64 × the mean (3 × with one solution), and progress can be shown honestly as x/8.
  • Bounded. work ≤ 2^48 for every epoch, far inside the 64-bit nonce space.

gasSum 390,000

verificationGasLimit · VGL
50,000
callGasLimit · CGL
80,000
paymasterVerificationGasLimit · pmVGL
75,000
paymasterPostOpGasLimit · postOp
25,000
preVerificationGas · PVG
160,000

work = 390,000 × 0.022 gwei × 5,000 / 1e9

= 42,900,000 hashes ≈ 225.4

per sub-solution = ⌈work / 8⌉ = 5,362,500 → 22 zero bits

8 sub-solutions≈ 222 hashes each

A first op on mainnet (a token transfer with its EIP-7702 delegation, the L1 data part included in PVG) at the configured floor: 390,000 gas priced at a 0.022 gwei base fee and 5,000 hashes per gwei is 42,900,000 expected hashes, split into 8 sub-solutions of about 22 leading zero bits each.

A first op on mainnet (a token transfer with its delegation, gasSum ≈ 390,000 with the L1 data part of PVG) at the 5,000 floor and a 0.022 gwei base fee costs about 2^25.4 hashes: around 3.6 s on a mid laptop (12 MH/s) and 11 s on a mid Android phone (3.75 MH/s). At the 30,000 ceiling (6×), about 21 s and 69 s; the p95 is 1.64 × these means, about 113 s on the phone.

Validation#

Validation reads only the op's epoch (4 storage slots, written once) and, with the delegate check, the sender's 23-byte delegation designator, on both paths. It never reads a nullifier: the relay refuses a spent stamp, and the chain records every one, so a double spend is public instead of possible. It reverts only on malformed data, an unknown epoch, or a prepaid stamp in an epoch without the STAMPS flag. Every other failure is soft: all checks of the op's path run, at constant gas, and the result is SIG_VALIDATION_FAILED. The same checks are exposed as checkUserOp(), which returns the failure bits:

Soft-failure bits (failMask)
BitNameSet when
1 << 0F_FEE_LOWmaxFeePerGas is below the epoch's minFeePerGas.
1 << 1F_FEE_HIGHmaxFeePerGas is above the epoch's fee cap.
1 << 2F_PRIORITYmaxPriorityFeePerGas is above the epoch's priority cap.
1 << 3F_COSTmaxCost (gasSum × maxFeePerGas) is above maxOpCost.
1 << 4F_PM_GASA paymaster gas limit differs from the epoch's exact value for the op's postage (pmPostOpGasLimit mined, stampPostOpGasLimit prepaid).
1 << 5F_NONCE_ORDERMined stamp: the 8 nonces are not strictly increasing.
1 << 6F_POWMined stamp: a sub-solution hash is above the per-solution target.
1 << 7F_COSIGThe relay co-signature is missing, wrong or high-s (for a prepaid stamp it must cover the nullifier too).
1 << 8F_GAS_CAPverificationGasLimit, callGasLimit or preVerificationGas is above the epoch's cap.
1 << 9F_DELEGATEThe sender is not delegated to Simple7702Account v0.9.
1 << 10F_NULLIFIERPrepaid stamp: the nullifier is 0 (reserved). A spent stamp is refused by the relay (STAMP_SPENT), not by validation: the paymaster records nullifiers in postOp and never reads them while validating.

Ops are limited to single calls from an allowlist of templates, enforced by the relay before it co-signs, for both postages:

Sponsored call templates on Robinhood Chain (198 listed tokens)
TemplateTargetCallSDK helper
transfereach listed token: WETH, USDG, cbBTC and 195 Robinhood stock tokenstransfer(address,uint256)transferCall(token, to, amount)
approveeach listed tokenapprove(address,uint256)approveCall(token, spender, amount)
sendEtha recipient without code, or an EOA delegated to Simple7702Account v0.9execute(to, value, 0x), value > 0sendEthCall(to, value)

Epochs, difficulty and budget#

  • Write-once epochs. A roll() (any EOA, once the epoch is over) creates the next epoch. Nothing validation reads ever changes afterwards, so a simulated op can't be invalidated by paymaster state, and the bundler never has a reason to ban it.
  • Two lanes in one deposit. The EntryPoint deposit holds the stamp float (what sold stamps prepaid and have not spent yet) and the free lane's share. roll() tops up only deposit − float, and the free lane's budget never counts the float: free stamps can never spend what buyers paid for.
  • Step controller. At each roll, utilization u of the free lane's budget sets the next difficulty: ×2 after two saturated epochs, ×1.25 above the 60% target, ×0.5 below half of it, ×1 otherwise, clamped to the configured range (6× on mainnet).
  • Budget = deposit share. The free lane is topped up to its epoch budget at most once per epoch, by a direct EOA roll, and an immutable MAX_DAILY_SPEND bounds the daily outflow. The relay paces co-signing of mined stamps across the epoch; prepaid stamps are admitted against the deposit's headroom only.