GasLimitedPaymaster
The GasLimitedPaymaster allows multiple transactions from a single deposit by tracking gas usage per nullifier.
How It Works
Multi-Use Model
- Single deposit enables multiple transactions
- Tracks actual gas cost per nullifier
- Reusable until joining amount is fully consumed
- More gas-efficient than OneTimeUse
State Tracking
mapping(uint256 => uint256) public nullifierGasUsage;
Tracks cumulative gas usage per nullifier instead of simple boolean.
Transaction Flow
1. User Joins Pool
Same as OneTimeUse - deposit joining amount (e.g., 0.0001 ETH) with identity commitment.
2. Transaction Execution
function _validatePaymasterUserOp(...) internal override returns (bytes memory context, uint256 validationData) {
// Verify ZK proof
// Check remaining gas allowance
uint256 currentUsage = nullifierGasUsage[proof.nullifier];
require(currentUsage < JOINING_AMOUNT, "Nullifier exhausted");
// Return context for post-op
return (abi.encode(userOpHash, proof.nullifier, sender), _packValidationData(false, 0, 0));
}
3. Post-Operation
function _postOp(
PostOpMode /*mode*/,
bytes calldata context,
uint256 actualGasCost,
uint256 actualUserOpFeePerGas
) internal virtual override {
// Decode the nullifier, userOpHash, sender from the context passed during validation.
(bytes32 userOpHash, uint256 nullifier, address sender) = abi.decode(
context,
(bytes32, uint256, address)
);
// Calculate the total gas cost, including the EntryPoint's postOp overhead.
uint256 postOpGasCost = Constants.POSTOP_GAS_COST *
actualUserOpFeePerGas;
uint256 totalGasCost = actualGasCost + postOpGasCost;
// Deduct the gas cost from the user's allowance tracked by their nullifier.
nullifierGasUsage[nullifier] += totalGasCost;
// Deduct from the global tracker of user deposits for revenue calculation.
totalDeposit -= totalGasCost;
emit UserOpSponsoredWithNullifier(
sender,
userOpHash,
totalGasCost,
nullifier
);
}
Key Features
- Multi-Use: Single deposit covers multiple transactions
- Gas Tracking: Only pays for actual gas used per transaction
- Reusable: Same nullifier until joining amount depleted
- Efficient: No waste of unused joining amount
Balance Checking
import { GAS_LIMITED_PAYMASTER_ABI } from '@prepaid-gas/constants'
// Query current usage
const currentUsage = await contract.read.nullifierGasUsage([nullifier])
const joiningAmount = await contract.read.JOINING_AMOUNT()
const remaining = joiningAmount - currentUsage
console.log(`Remaining gas allowance: ${remaining} wei`)
Contract Address
Base Sepolia: 0xDEc68496A556CeE996894ac2FDc9E43F39938e62
Source Code
- GitHub: GasLimitedPaymaster.sol