Use third-party on-chain paymasters for ERC-20 gas payments. This approach gives you flexibility to use different paymaster providers while still benefiting from Gelato’s high-performance bundler.
On-chain paymasters are smart contracts that sponsor gas fees by accepting ERC-20 tokens from users. They handle the token-to-ETH conversion on-chain, allowing users to pay gas with tokens they hold.
Implementations
Permissionless
API Endpoints
This example uses Pimlico’s ERC-20 Paymaster. You can substitute any compatible on-chain paymaster.npm install permissionless viem
Create an API Key
Check out our How-To Guide for detailed instructions on generating a Gelato API key.You’ll also need an API key from your paymaster provider (e.g., Pimlico). Create Smart Account
import { createSmartAccountClient } from "permissionless";
import { createPimlicoClient } from "permissionless/clients/pimlico";
import { prepareUserOperationForErc20Paymaster } from "permissionless/actions/erc20";
import { createPublicClient, http, type Hex } from "viem";
import { entryPoint07Address, toSoladySmartAccount } from "viem/account-abstraction";
import { privateKeyToAccount } from "viem/accounts";
import { baseSepolia } from "viem/chains";
const owner = privateKeyToAccount(process.env.PRIVATE_KEY as Hex);
const client = createPublicClient({
chain: baseSepolia,
transport: http(),
});
const account = await toSoladySmartAccount({
client,
owner,
});
Create Paymaster Client
Set up the on-chain paymaster client. This example uses Pimlico:const pimlicoUrl = `https://api.pimlico.io/v2/${baseSepolia.id}/rpc?apikey=${process.env.PIMLICO_API_KEY}`;
const paymasterClient = createPimlicoClient({
chain: baseSepolia,
transport: http(pimlicoUrl),
entryPoint: {
address: entryPoint07Address,
version: "0.7",
},
});
Create Smart Account Client with Gelato Bundler
Combine Gelato’s bundler with the on-chain paymaster:const bundlerUrl = `https://api.t.gelato.cloud/rpc/${baseSepolia.id}`;
const smartClient = createSmartAccountClient({
account,
chain: baseSepolia,
bundlerTransport: http(bundlerUrl, {
fetchOptions: {
headers: {
'X-API-Key': process.env.GELATO_API_KEY
}
}
}),
paymaster: paymasterClient,
userOperation: {
estimateFeesPerGas: async ({ bundlerClient }) => {
const gasPrices = await bundlerClient.request({
method: "eth_getUserOperationGasPrice",
params: [],
});
return {
maxFeePerGas: BigInt(gasPrices.maxFeePerGas),
maxPriorityFeePerGas: BigInt(gasPrices.maxPriorityFeePerGas),
};
},
prepareUserOperation: prepareUserOperationForErc20Paymaster(paymasterClient),
},
});
Send Transaction
const USDC_ADDRESS = "0x036CbD53842c5426634e7929541eC2318f3dCF7e"; // USDC (Base Sepolia)
const txHash = await smartClient.sendTransaction({
to: "0xE27C1359cf02B49acC6474311Bd79d1f10b1f8De",
value: 0n,
data: "0xd09de08a", // increment()
paymasterContext: {
token: USDC_ADDRESS,
},
});
console.log(`Transaction hash: ${txHash}`);
When using the API directly, you need to obtain paymaster fields from your chosen paymaster provider and include them in the UserOperation.Create API Keys
You’ll need:
- A Gelato API key from app.gelato.cloud
- A paymaster API key from your provider (e.g., Pimlico, Alchemy)
Get Paymaster Data
Request sponsorship data from your paymaster. Example with Pimlico:const USDC_ADDRESS = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
const paymasterResponse = await fetch(
`https://api.pimlico.io/v2/${chainId}/rpc?apikey=${PIMLICO_API_KEY}`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "pm_getPaymasterStubData",
params: [
{
sender: "0xYourSmartAccountAddress",
nonce: "0x0",
callData: "0xCalldata",
maxFeePerGas: "0x...",
maxPriorityFeePerGas: "0x..."
},
"0x0000000071727De22E5E9d8BAf0edAc6f37da032", // EntryPoint v0.7
chainId.toString(),
{ token: USDC_ADDRESS }
]
})
}
);
const paymasterData = await paymasterResponse.json();
// Returns: paymaster, paymasterData, paymasterVerificationGasLimit, paymasterPostOpGasLimit
Build UserOperation with Paymaster Fields
Include the paymaster fields in your UserOperation:const userOperation = {
sender: "0xYourSmartAccountAddress",
nonce: "0x0",
callData: "0xCalldata",
callGasLimit: "0x...",
verificationGasLimit: "0x...",
preVerificationGas: "0x...",
maxFeePerGas: "0x...",
maxPriorityFeePerGas: "0x...",
signature: "0xSignature",
// Paymaster fields from step 2
paymaster: paymasterData.result.paymaster,
paymasterData: paymasterData.result.paymasterData,
paymasterVerificationGasLimit: paymasterData.result.paymasterVerificationGasLimit,
paymasterPostOpGasLimit: paymasterData.result.paymasterPostOpGasLimit,
};
Send to Gelato Bundler
const response = await fetch(
`https://api.t.gelato.cloud/rpc/${chainId}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": process.env.GELATO_API_KEY
},
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "eth_sendUserOperation",
params: [
userOperation,
"0x0000000071727De22E5E9d8BAf0edAc6f37da032" // EntryPoint v0.7
]
})
}
);
const data = await response.json();
const userOpHash = data.result;
console.log(`UserOperation hash: ${userOpHash}`);
Get Receipt
const response = await fetch(
`https://api.t.gelato.cloud/rpc/${chainId}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": process.env.GELATO_API_KEY
},
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "eth_getUserOperationReceipt",
params: [userOpHash]
})
}
);
const data = await response.json();
if (data.result) {
console.log(`Transaction hash: ${data.result.receipt.transactionHash}`);
}
How It Works
- Request Sponsorship - Your app requests paymaster data from the on-chain paymaster provider
- Build UserOperation - Include the paymaster fields (
paymaster, paymasterData, gas limits)
- Submit to Bundler - Gelato validates and bundles the UserOperation
- On-Chain Execution - The paymaster validates the operation and charges ERC-20 tokens
Compatible Paymasters
Any ERC-4337 compatible paymaster works with Gelato’s bundler:
Additional Resources