Gasless Transactions Setup (Gas Sponsorship)
Web3 onboarding breaks at one point: a user registers, passes KYC, connects a wallet — and sees they need ETH for gas on their first action. They don't have it. Conversion drops. Gasless transactions solve this by shifting gas payment to the protocol or sponsor.
There are three implementation variants, and the choice between them isn't obvious.
Three Gasless Architectures: When to Use What
Meta-transactions (EIP-2771)
User signs data off-chain (no transaction), relayer takes the signature, wraps it in a transaction and pays gas themselves. Contract via ERC2771Context extracts the original sender from calldata instead of msg.sender.
import "@openzeppelin/contracts/metatx/ERC2771Context.sol";
contract MyContract is ERC2771Context {
constructor(address trustedForwarder) ERC2771Context(trustedForwarder) {}
function doSomething() external {
address sender = _msgSender(); // actual user, not relayer
// logic
}
}
When suitable: existing contract with minimal changes. Simple logic without account abstraction. Minus — relayer is centralized and you either host it or pay for a service (Gelato, Biconomy).
Hidden problem: if contract checks msg.sender in multiple places and part of the code isn't adapted to _msgSender() — authorization errors at production load. We've seen this on a project where 15% of transactions failed exactly because of this.
ERC-4337 Account Abstraction + Paymaster
ERC-4337 is an account abstraction standard without Ethereum consensus changes. User works through a smart account (wallet contract), not an EOA. Paymaster is a smart contract that takes on gas payment for a user under specified rules.
System components:
| Component | Role |
|---|---|
| UserOperation | "transaction" from user (not a real tx) |
| Bundler | collects UserOps and sends a real transaction |
| EntryPoint | global coordinator contract (0x5FF1...7780) |
| Paymaster | decides whether to sponsor gas for a specific UserOp |
| Smart Account | user's wallet (Safe, Biconomy, ZeroDev) |
Paymaster can sponsor gas conditionally: only for first N transactions, only for token holders, only for whitelist addresses.
contract MyPaymaster is BasePaymaster {
mapping(address => bool) public whitelist;
function _validatePaymasterUserOp(
UserOperation calldata userOp,
bytes32 userOpHash,
uint256 maxCost
) internal override returns (bytes memory context, uint256 validationData) {
require(whitelist[userOp.sender], "Not whitelisted");
return ("", 0); // 0 = success
}
}
When suitable: new dApp from scratch with full UX control needed. Social login (email via WebAuthn + smart account). Transaction batching (multiple operations in one UserOp). Wallet recovery without seed phrase.
Complexity: the ERC-4337 ecosystem is young. You either host your own bundler or use Alchemy, Pimlico, Stackup. Staking Paymaster requires ETH deposit in EntryPoint. Debugging UserOp failures is its own art.
Custom Relayer
Simplest option: backend service with a wallet that accepts signed requests from users and sends transactions, paying gas.
Suitable for closed apps (B2B, enterprise solutions) where users are known and verified. Centralized — the relayer wallet becomes a point of failure and target.
What to Choose
| Criteria | Meta-tx (EIP-2771) | ERC-4337 | Custom Relayer |
|---|---|---|---|
| Integration complexity | Medium | High | Low |
| Decentralization | Medium | High | Low |
| UX possibilities | Basic | Extended | Basic |
| Development cost | Low | High | Minimal |
| Existing contract | Yes (with changes) | No | Yes |
Implementing ERC-4337 with Pimlico
For a new project — we recommend Pimlico as bundler + paymaster provider. Integration via permissionless.js:
import { createSmartAccountClient } from "permissionless";
import { signerToSimpleSmartAccount } from "permissionless/accounts";
import { createPimlicoPaymasterClient } from "permissionless/clients/pimlico";
const paymasterClient = createPimlicoPaymasterClient({
transport: http(`https://api.pimlico.io/v2/${chainId}/rpc?apikey=${PIMLICO_KEY}`),
entryPoint: ENTRYPOINT_ADDRESS_V07,
});
const smartAccount = await signerToSimpleSmartAccount(publicClient, {
signer: walletClient,
factoryAddress: SIMPLE_ACCOUNT_FACTORY,
entryPoint: ENTRYPOINT_ADDRESS_V07,
});
const smartAccountClient = createSmartAccountClient({
account: smartAccount,
entryPoint: ENTRYPOINT_ADDRESS_V07,
chain: mainnet,
bundlerTransport: http(bundlerUrl),
middleware: {
sponsorUserOperation: paymasterClient.sponsorUserOperation,
},
});
// Gasless transaction for user
const txHash = await smartAccountClient.sendTransaction({
to: contractAddress,
data: encodeFunctionData({ abi, functionName: "doSomething", args: [] }),
});
Monitoring and Limits
Gasless is sponsorship, and without limits your budget disappears fast. Configure:
- maximum gas per UserOp
- daily limit per address
- global daily limit
- monitoring Paymaster balance + auto-refill
Paymaster that runs out of deposit starts rejecting all transactions. Users see "gasless transaction unavailable" without explanation — bad UX. Alerts are mandatory.
Timeline for basic gasless scheme via meta-transactions — 3-4 days. Full ERC-4337 integration with custom Paymaster, frontend and monitoring — 4-5 days.







