
Gas sponsorship is one of the features introduced by ERC-4337. It allows developers to completely abstract away gas costs from their users by sponsoring transactions or by letting users pay fees in ERC-20 tokens instead of native ETH. This deep dive explores the full technical landscape of paymaster-based gas sponsorship as implemented in Openfort's PaymasterV3EPv9 contracts.

Background: The Paymaster in ERC-4337
In the ERC-4337 architecture, a Paymaster is a smart contract that can pay for another user's transaction gas fees. When the paymasterAndData field in a UserOperation is not empty, the EntryPoint triggers an alternative flow:
- During validation, the EntryPoint deducts the required ETH prefund from the paymaster's deposit (not from the sender) and calls
validatePaymasterUserOp()on the paymaster contract. - During execution, the user's intended call is forwarded to the smart account.
- During post-execution, the EntryPoint calls
postOp()on the paymaster, allowing it to finalize accounting, refund excess tokens, collect ERC-20 payments, or emit sponsorship events.
This three-phase lifecycle (
validatePaymasterUserOp→ execution →postOp) is the foundation of all paymaster implementations.
Two Sponsorship Modes (PaymasterV3.sol)
Openfort's paymaster supports two top-level sponsorship modes, each serving a fundamentally different use case:
| Mode | Byte | Description |
|---|---|---|
| Verifying Mode | 0x00 | The paymaster fully sponsors the user's gas. The user pays nothing. |
| ERC-20 Mode | 0x01 | The user pays for gas with ERC-20 tokens (e.g. USDC, USDT). The paymaster fronts the ETH cost and collects equivalent token value from the user in postOp. |
The mode byte is the first byte of the
paymasterData(the data appended after the paymaster address inpaymasterAndData), and it determines the entire downstream parsing and execution logic.

_10 const paymasterData = concat([_10 PaymasterData.MODE,_10 PaymasterData.VALID_UNTIL,_10 PaymasterData.VALID_AFTER_10 ]);
Verifying Mode: Full Gas Sponsorship
In Verifying Mode, a designated off-chain signer (controlled by Openfort) evaluates whether a given UserOperation qualifies for sponsorship based on configurable policies rules that can target specific contracts, functions, time windows, gas budgets, or user segments.
If the operation qualifies, the signer produces a signature over the full UserOperation and relevant paymaster fields. This signature is embedded in the paymasterData and verified on-chain during validatePaymasterUserOp().
paymasterData Structure (Verifying Mode)

_10MODE_VERIFYING (1 byte) - 0x00_10validUntil (6 bytes)_10validAfter (6 bytes)_10signature (65 bytes)
Lifecycle
_10VALIDATION: EntryPoint deducts requiredPrefund from paymaster deposit._10 Paymaster verifies the off-chain signer's signature._10 Returns (context, validationData) with time bounds._10_10EXECUTION: UserOp callData is forwarded to the smart account._10_10POSTOP: Paymaster emits a UserOperationSponsored event._10 Any unused gas from the prefund is refunded to the paymaster's_10 EntryPoint deposit automatically by the EntryPoint.
Policy Engine
Openfort's policy system allows granular control over sponsorship.
These policies are evaluated off-chain. If a UserOperation satisfies the active policy rules, the signer signs over it. If it does not, the API rejects the sponsorship request and the user must either self-fund or use ERC-20 mode.
When to Use Verifying Mode
Verifying mode is ideal when the application developer wants to sponsor gas costs entirely common in onboarding flows, gaming, loyalty programs, or any context where requiring users to hold ETH would create friction. The paymaster's deposit at the EntryPoint must be periodically replenished by the developer.
ERC-20 Mode: Paying Gas with Tokens
In ERC-20 Mode, users pay for their own gas but in ERC-20 tokens rather than native ETH. The paymaster fronts the ETH to the bundler and then collects the equivalent token amount from the user during postOp(), based on an exchange rate provided by the off-chain signer.
This mode supports eight sub-modes controlled by a single combinedByte a bitmask with three feature flags:
| Bit | Flag | Feature | Description |
|---|---|---|---|
| 0 | 0x01 | constantFeePresent | Adds a fixed protocol fee on top of the gas cost |
| 1 | 0x02 | recipientPresent | Specifies an address to receive excess tokens |
| 2 | 0x04 | preFundPresent | Charges tokens upfront during validation, reconciles in postOp |
_11 const paymasterData = concat([_11 PaymasterData.MODE_ERC20,_11 PaymasterData.COMBINED_BYTE_BASIC,_11 PaymasterData.VALID_UNTIL,_11 PaymasterData.VALID_AFTER,_11 PaymasterData.ERC20_ADDRESS,_11 pad(toHex(PaymasterData.POST_GAS_LIMIT), { size: 16 }),_11 pad(toHex(BigInt(PaymasterData.EXCHANGE_RATE)), { size: 32 }),_11 pad(toHex(PaymasterData.PAYMASTER_VALIDATION_GAS_LIMIT), { size: 16 }),_11 PaymasterData.TREASURY,_11 ]);
Base paymasterData Structure (ERC-20 Mode)
All ERC-20 sub-modes share a common base layout:
_10MODE_ERC20 (1 byte) - 0x01_10COMBINED_BYTE (1 byte) - 0x00 ... 0x07_10validUntil (6 bytes)_10validAfter (6 bytes)_10token (20 bytes) - ERC-20 token address_10postOpGas (16 bytes) - gas reserved for postOp execution_10exchangeRate (32 bytes) - token-per-wei rate (scaled by 1e18)_10paymasterValidationGasLimit (16 bytes)_10treasury (20 bytes) - address that receives gas payments
Optional fields are appended after the treasury in a strict order enforced by the Solidity parsing logic in _parseErc20Config():
_101. preFundInToken (16 bytes) - if preFundPresent (bit 2)_102. constantFee (16 bytes) - if constantFeePresent (bit 0)_103. recipient (20 bytes) - if recipientPresent (bit 1)
The signature (65 bytes) follows after all optional fields.
Core ERC-20 Token Payment Flow
Before examining the eight individual sub-modes, it is important to understand the core payment mechanism that underpins all of them.
During validatePaymasterUserOp():
The paymaster verifies the off-chain signer's signature over the UserOperation and all paymaster-specific fields (token address, exchange rate, treasury, optional fields). If preFundPresent is set, a safeTransferFrom is executed to collect preFundInToken from the user immediately. Otherwise, no token transfer occurs during validation the paymaster relies on the user's pre-existing approval to collect tokens in postOp.
During postOp():
The paymaster calculates the actual gas cost of the operation, converts it to token value using the exchange rate, adds any constantFee, and settles the difference between what was pre-funded (if anything) and what was actually owed. The settlement uses bidirectional safeTransferFrom calls:
- If the user owes more than the preFund: tokens are transferred from the user to the treasury.
- If the user was overcharged: tokens are refunded from the treasury back to the user.
- If a
recipientis set andpreFundInToken > actualCost: excess tokens go to the recipient instead of back to the user.
The cost-in-token calculation includes a penalty gas estimate that accounts for the EntryPoint's 10% penalty on unused execution gas a critical detail that prevents the paymaster from being undercharged.
_10costInToken = getCostInToken(actualGasCost + penaltyGasCost, postOpGas,_10 actualUserOpFeePerGas, exchangeRate) + constantFee
The Eight ERC-20 Sub-Modes
Mode 0x00 — Basic
The simplest ERC-20 mode. The user pays the exact gas cost in tokens after execution. No constant fee, no recipient, no pre-funding.
Combined Byte: 0x00 (binary: 000)
Flow:
_10VALIDATION: Signature verified. No token transfer._10EXECUTION: UserOp executes._10POSTOP: actualGasCost converted to tokens → transferred from user to treasury.
Use cases: Straightforward gas payment in stablecoins (USDC, USDT, DAI). The user approves the paymaster in advance, and pays only for what they consume.
Example: ERC20-Sponsored/executeCall.ERC20.ts
_10Gas used: 0.80 USDC_10User pays: 0.80 USDC → treasury
Mode 0x01 — ConstantFee
Adds a fixed protocol fee on top of the actual gas cost. This fee is denominated in the ERC-20 token and is independent of gas consumption.
Combined Byte: 0x01 (binary: 001)
Additional paymasterData field:
_10constantFee (16 bytes)
Flow:
_10VALIDATION: Signature verified. No token transfer._10EXECUTION: UserOp executes._10POSTOP: User pays (actualGasCost + constantFee) in tokens → treasury.
Use cases: Revenue generation per transaction, protocol service fees, subscription-like models where each operation carries a flat surcharge.
Example: ERC20-Sponsored/executeCall.ERC20.ConstantFee.ts
_10Gas used: 0.80 USDC_10Constant fee: 0.10 USDC_10User pays: 0.90 USDC → treasury
Mode 0x02 — Recipient
Introduces a recipient address that receives excess tokens when a pre-funded amount exceeds the actual cost. In this mode, without preFundPresent, the recipient mechanism activates based on the computed preFundInToken derived from the max gas cost.
Combined Byte: 0x02 (binary: 010)
Additional paymasterData field:
_10recipient (20 bytes)
Flow:
_10VALIDATION: Signature verified. No token transfer._10EXECUTION: UserOp executes._10POSTOP: User pays actualGasCost in tokens._10 If preFundInToken > actualCost → excess sent to recipient.
Use cases: Referral systems where the dApp earns from gas overestimation, fee-sharing models, donation mechanisms where excess is routed to a charity address.
Example: ERC20-Sponsored/executeCall.ERC20.Recipient.ts
_10Reserved (preFund): 1.50 USDC_10Gas used: 0.90 USDC_10Excess: 0.60 USDC → recipient address
Mode 0x03 — ConstantFee + Recipient
Combines the fixed protocol fee with the recipient mechanism. The user pays actualGasCost + constantFee, and any excess beyond this total is sent to the recipient.
Combined Byte: 0x03 (binary: 011)
Additional paymasterData fields:
_10constantFee (16 bytes)_10recipient (20 bytes)
Flow:
_10VALIDATION: Signature verified. No token transfer._10EXECUTION: UserOp executes._10POSTOP: User pays (actualGasCost + constantFee)._10 Excess → recipient.
Use cases: Protocol revenue plus referral earnings, tiered revenue models with a base fee and variable earnings from excess.
Example: ERC20-Sponsored/executeCall.ERC20.ConstantFeeRecipient.ts
_10Reserved (preFund): 1.50 USDC_10Gas used: 0.80 USDC_10Constant fee: 0.10 USDC_10Total cost: 0.90 USDC_10Excess: 0.60 USDC → recipient
Mode 0x04 — PreFund
Charges tokens upfront during the validation phase by executing a safeTransferFrom before the UserOp is executed. In postOp, the actual cost is calculated and the difference is reconciled either the user pays more or the treasury refunds the overpayment.
Combined Byte: 0x04 (binary: 100)
Additional paymasterData field:
_10preFundInToken (16 bytes)
Flow:
_10VALIDATION: preFundInToken transferred from user → treasury via safeTransferFrom._10EXECUTION: UserOp executes._10POSTOP: Calculate actualCost._10 If actualCost > preFund → user pays difference to treasury._10 If actualCost < preFund → treasury refunds difference to user.
Important: This mode requires a higher paymasterVerificationGasLimit (200,000+) because the safeTransferFrom during validation consumes significantly more gas than a signature-only check.
Use cases: Escrow patterns, budget control (user caps maximum spend), trust-building UX where users see the exact upfront cost, enterprise prepaid gas for batch operations.
Example: ERC20-Sponsored/executeCall.ERC20.PreFund.ts
_10VALIDATION: User deposits 2.00 USDC → treasury_10_10Case A — Under budget:_10 Gas used: 0.80 USDC_10 Refund: 1.20 USDC → back to user_10_10Case B — Over budget:_10 Gas used: 2.50 USDC_10 User pays: 0.50 USDC additional → treasury
Mode 0x05 — PreFund + ConstantFee
Combines upfront deposit with a fixed protocol fee. The total cost is actualGasCost + constantFee, reconciled against the pre-funded amount.
Combined Byte: 0x05 (binary: 101)
Additional paymasterData fields:
_10preFundInToken (16 bytes)_10constantFee (16 bytes)
Flow:
_10VALIDATION: preFundInToken transferred from user → treasury._10EXECUTION: UserOp executes._10POSTOP: totalCost = actualGasCost + constantFee._10 Reconcile preFund vs totalCost (refund or charge difference).
Use cases: Enterprise deposits with per-transaction service charges, subscription-plus-usage billing models.
Example: ERC20-Sponsored/executeCall.ERC20.PreFundConstantFee.ts
_10VALIDATION: User deposits 2.00 USDC → treasury_10_10Gas used: 0.80 USDC_10Constant fee: 0.10 USDC_10Total cost: 0.90 USDC_10Refund: 1.10 USDC → back to user
Mode 0x06 — PreFund + Recipient
Upfront deposit where excess goes to a recipient instead of being refunded to the user. This is a key distinction from Mode 0x04 the user does not get a refund if they overpay.
Combined Byte: 0x06 (binary: 110)
Additional paymasterData fields:
_10preFundInToken (16 bytes)_10recipient (20 bytes)
Flow:
_10VALIDATION: preFundInToken transferred from user → treasury._10EXECUTION: UserOp executes._10POSTOP: If actualCost < preFund → excess sent to recipient (NOT back to user).
Use cases: Referral-plus-escrow models, protocol revenue capture from gas overestimation, donation mode where excess is automatically forwarded.
Example: ERC20-Sponsored/executeCall.ERC20.PreFundRecipient.ts
_10VALIDATION: User deposits 2.00 USDC → treasury_10_10Gas used: 0.80 USDC_10Excess: 1.20 USDC → recipient (dApp/protocol wallet)
Mode 0x07 — ALL (PreFund + ConstantFee + Recipient)
The fully-loaded mode with all three optional features enabled. Pre-funds tokens during validation, adds a fixed protocol fee, and routes any excess to a designated recipient.
Combined Byte: 0x07 (binary: 111)
Additional paymasterData fields:
_10preFundInToken (16 bytes)_10constantFee (16 bytes)_10recipient (20 bytes)
Flow:
_10VALIDATION: preFundInToken transferred from user → treasury._10EXECUTION: UserOp executes._10POSTOP: totalCost = actualGasCost + constantFee._10 If preFund > totalCost → excess sent to recipient.
Use cases: Full revenue models with multiple streams per transaction, enterprise billing with pre-funding, service fee, and operational wallet capture.
Example: ERC20-Sponsored/executeCall.ERC20.All.ts
_10VALIDATION: User deposits 3.00 USDC → treasury_10_10Gas used: 0.80 USDC_10Constant fee: 0.10 USDC_10Total cost: 0.90 USDC_10Excess: 2.10 USDC → recipient
Gas Considerations
The choice of sub-mode has direct implications on gas limits that must be set in the UserOperation:
Standard Modes (0x00, 0x01, 0x02, 0x03)
These modes perform no token transfer during validation only a signature verification. The paymasterVerificationGasLimit can be set to a standard value:
_10PAYMASTER_VALIDATION_GAS_LIMIT: 100_000
PreFund Modes (0x04, 0x05, 0x06, 0x07)
These modes execute a safeTransferFrom during validation, which involves an external call to the ERC-20 token contract. This requires significantly more gas:
_10PAYMASTER_VALIDATION_GAS_LIMIT_PREFUND: 200_000
If gas estimation is performed before the paymaster data is finalized, the paymasterVerificationGasLimit must be overridden after estimation to accommodate the validation-phase transfer. Failure to do so will result in the UserOperation reverting during validation with an out-of-gas error.
Gas Penalty Accounting
The EntryPoint charges a 10% penalty on unused execution gas. If the UserOperation specifies a callGasLimit of 1,000,000 but only uses 100,000, the EntryPoint charges 10% of the 900,000 unused gas as a penalty. This penalty is deducted from the paymaster's deposit, not from the sender.
If the paymaster's postOp calculation does not account for this penalty, the paymaster will systematically undercharge users a drain that compounds over time. The Openfort paymaster includes an _expectedPenaltyGasCost calculation that estimates the penalty based on actualGasCost, postOpGas, preOpGasApproximation, and executionGasLimit, ensuring the token charge to the user covers the full cost including penalties.
Exchange Rate Trust
The exchange rate in ERC-20 mode is provided by the off-chain signer and embedded in the signed paymasterData. This means the user trusts Openfort to provide a fair rate. The rate is not derived from an on-chain oracle at execution time this is a deliberate design choice that avoids oracle manipulation risks and reduces on-chain gas consumption, but introduces a trust assumption on the signer's rate feed.
ERC-20 Mode Quick Reference
_17┌────────┬───────────────────────────────────────────────────────────────────┐_17│ 0x00 │ Basic — Simple ERC-20 gas payment │_17├────────┼───────────────────────────────────────────────────────────────────┤_17│ 0x01 │ ConstantFee — Gas + fixed protocol fee │_17├────────┼───────────────────────────────────────────────────────────────────┤_17│ 0x02 │ Recipient — Excess tokens go to specified address │_17├────────┼───────────────────────────────────────────────────────────────────┤_17│ 0x03 │ ConstantFee + Recipient — Fee + excess to recipient │_17├────────┼───────────────────────────────────────────────────────────────────┤_17│ 0x04 │ PreFund — Upfront deposit, reconcile in postOp │_17├────────┼───────────────────────────────────────────────────────────────────┤_17│ 0x05 │ PreFund + ConstantFee — Deposit + fee │_17├────────┼───────────────────────────────────────────────────────────────────┤_17│ 0x06 │ PreFund + Recipient — Deposit, excess to recipient │_17├────────┼───────────────────────────────────────────────────────────────────┤_17│ 0x07 │ ALL — Deposit + fee + excess to recipient │_17└────────┴───────────────────────────────────────────────────────────────────┘
Ready to implement gas sponsorship for your users? Explore Openfort's Account Abstraction infrastructure for the complete toolkit including paymasters, bundlers, and smart accounts.
Related reading
- Exploring EIP-7702 and how it brings smart account features to EOAs
- Session keys for smooth UX and how they complement gas sponsorship
- EOA vs smart wallet: choosing the right account model for your application
- Openfort PaymasterV3EPv9 source code on GitHub
- ERC-4337 specification on the Ethereum EIPs repository
