Technical Dive: Gas Sponsorship via Paymasters

0xKoiner13 min read
Technical Dive: Gas Sponsorship via Paymasters

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.

Paymaster_1.png

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:

  1. 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.
  2. During execution, the user's intended call is forwarded to the smart account.
  3. 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:

ModeByteDescription
Verifying Mode0x00The paymaster fully sponsors the user's gas. The user pays nothing.
ERC-20 Mode0x01The 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 in paymasterAndData), and it determines the entire downstream parsing and execution logic.

Paymaster_3.png


_10
const paymasterData = concat([
_10
PaymasterData.MODE,
_10
PaymasterData.VALID_UNTIL,
_10
PaymasterData.VALID_AFTER
_10
]);

ETH-Sponsored/executeCall.ts

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)

Paymaster_4.png


_10
MODE_VERIFYING (1 byte) - 0x00
_10
validUntil (6 bytes)
_10
validAfter (6 bytes)
_10
signature (65 bytes)

Lifecycle


_10
VALIDATION: EntryPoint deducts requiredPrefund from paymaster deposit.
_10
Paymaster verifies the off-chain signer's signature.
_10
Returns (context, validationData) with time bounds.
_10
_10
EXECUTION: UserOp callData is forwarded to the smart account.
_10
_10
POSTOP: 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:

BitFlagFeatureDescription
00x01constantFeePresentAdds a fixed protocol fee on top of the gas cost
10x02recipientPresentSpecifies an address to receive excess tokens
20x04preFundPresentCharges 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:


_10
MODE_ERC20 (1 byte) - 0x01
_10
COMBINED_BYTE (1 byte) - 0x00 ... 0x07
_10
validUntil (6 bytes)
_10
validAfter (6 bytes)
_10
token (20 bytes) - ERC-20 token address
_10
postOpGas (16 bytes) - gas reserved for postOp execution
_10
exchangeRate (32 bytes) - token-per-wei rate (scaled by 1e18)
_10
paymasterValidationGasLimit (16 bytes)
_10
treasury (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():


_10
1. preFundInToken (16 bytes) - if preFundPresent (bit 2)
_10
2. constantFee (16 bytes) - if constantFeePresent (bit 0)
_10
3. 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 recipient is set and preFundInToken > 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.


_10
costInToken = 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:


_10
VALIDATION: Signature verified. No token transfer.
_10
EXECUTION: UserOp executes.
_10
POSTOP: 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


_10
Gas used: 0.80 USDC
_10
User 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:


_10
constantFee (16 bytes)

Flow:


_10
VALIDATION: Signature verified. No token transfer.
_10
EXECUTION: UserOp executes.
_10
POSTOP: 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


_10
Gas used: 0.80 USDC
_10
Constant fee: 0.10 USDC
_10
User 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:


_10
recipient (20 bytes)

Flow:


_10
VALIDATION: Signature verified. No token transfer.
_10
EXECUTION: UserOp executes.
_10
POSTOP: 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


_10
Reserved (preFund): 1.50 USDC
_10
Gas used: 0.90 USDC
_10
Excess: 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:


_10
constantFee (16 bytes)
_10
recipient (20 bytes)

Flow:


_10
VALIDATION: Signature verified. No token transfer.
_10
EXECUTION: UserOp executes.
_10
POSTOP: 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


_10
Reserved (preFund): 1.50 USDC
_10
Gas used: 0.80 USDC
_10
Constant fee: 0.10 USDC
_10
Total cost: 0.90 USDC
_10
Excess: 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:


_10
preFundInToken (16 bytes)

Flow:


_10
VALIDATION: preFundInToken transferred from user → treasury via safeTransferFrom.
_10
EXECUTION: UserOp executes.
_10
POSTOP: 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


_10
VALIDATION: User deposits 2.00 USDC → treasury
_10
_10
Case A — Under budget:
_10
Gas used: 0.80 USDC
_10
Refund: 1.20 USDC → back to user
_10
_10
Case 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:


_10
preFundInToken (16 bytes)
_10
constantFee (16 bytes)

Flow:


_10
VALIDATION: preFundInToken transferred from user → treasury.
_10
EXECUTION: UserOp executes.
_10
POSTOP: 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


_10
VALIDATION: User deposits 2.00 USDC → treasury
_10
_10
Gas used: 0.80 USDC
_10
Constant fee: 0.10 USDC
_10
Total cost: 0.90 USDC
_10
Refund: 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:


_10
preFundInToken (16 bytes)
_10
recipient (20 bytes)

Flow:


_10
VALIDATION: preFundInToken transferred from user → treasury.
_10
EXECUTION: UserOp executes.
_10
POSTOP: 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


_10
VALIDATION: User deposits 2.00 USDC → treasury
_10
_10
Gas used: 0.80 USDC
_10
Excess: 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:


_10
preFundInToken (16 bytes)
_10
constantFee (16 bytes)
_10
recipient (20 bytes)

Flow:


_10
VALIDATION: preFundInToken transferred from user → treasury.
_10
EXECUTION: UserOp executes.
_10
POSTOP: 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


_10
VALIDATION: User deposits 3.00 USDC → treasury
_10
_10
Gas used: 0.80 USDC
_10
Constant fee: 0.10 USDC
_10
Total cost: 0.90 USDC
_10
Excess: 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:


_10
PAYMASTER_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:


_10
PAYMASTER_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.

Share this article

Keep Reading