Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

How to sponsor your users' transaction fees on any EVM chain

This guide shows you how to pay your users' transaction fees on Ethereum, Base, Arbitrum, Optimism, Polygon, BNB Chain, Avalanche or any other supported EVM chain, so they can transact without holding the chain's native token. You send each user's transactions as an ERC-4337 user operation, and Pimlico's verifying paymaster pays the gas from your Pimlico balance. The code is the same on every chain: only the chain import changes. For Solana, see Solana gas sponsorship.

Prerequisites

  • A Pimlico API key. Sponsoring on mainnets needs a paid plan with a balance or card attached; testnets are free. Create one.
npm
npm install permissionless viem

Steps

Pick your chain

Import your chain from viem/chains as chain. The Pimlico URL in the next step is built from chain.id, so nothing else changes.

Ethereum
import { mainnet as chain } from "viem/chains" // chain ID 1

For any other chain, find its chain ID on the supported chains page and its viem export in viem's chain list.

Create the clients

Create a public client, a Pimlico client (used as both bundler and paymaster), the user's smart account, and a smart account client. Replace the owner with the user's signer, such as an embedded wallet or a passkey.

import { createSmartAccountClient } from "permissionless"
import { toSimpleSmartAccount } from "permissionless/accounts"
import { createPimlicoClient } from "permissionless/clients/pimlico"
import { createPublicClient, type Hex, http } from "viem"
import { entryPoint07Address } from "viem/account-abstraction"
import { privateKeyToAccount } from "viem/accounts"
import { mainnet as chain } from "viem/chains" // the only line that changes per chain
 
const pimlicoUrl = `https://api.pimlico.io/v2/${chain.id}/rpc?apikey=YOUR_PIMLICO_API_KEY`
 
const publicClient = createPublicClient({
	chain,
	transport: http(), // use your own RPC URL in production
})
 
const pimlicoClient = createPimlicoClient({
	chain,
	transport: http(pimlicoUrl),
	entryPoint: {
		address: entryPoint07Address,
		version: "0.7",
	},
})
 
// Replace with the user's signer (embedded wallet, passkey, etc.)
const owner = privateKeyToAccount(process.env.PRIVATE_KEY as Hex)
 
const account = await toSimpleSmartAccount({
	client: publicClient,
	owner,
	entryPoint: {
		address: entryPoint07Address,
		version: "0.7",
	},
})
 
const smartAccountClient = createSmartAccountClient({
	account,
	chain,
	bundlerTransport: http(pimlicoUrl),
	paymaster: pimlicoClient,
	userOperation: {
		estimateFeesPerGas: async () => {
			return (await pimlicoClient.getUserOperationGasPrice()).fast
		},
	},
})

If your users already have an EOA with funds in it, see Sponsor an existing EOA with EIP-7702 instead.

Send a sponsored user operation

Send the user's calls with sendUserOperation. Pimlico sponsors the gas because the client's paymaster is the Pimlico client. Put everything the user is doing in one calls array, so the fixed cost of a user operation is paid once rather than per call.

// Batch everything the user is doing into one user operation to pay the overhead once
const userOpHash = await smartAccountClient.sendUserOperation({
	calls: [
		{
			to: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
			value: 0n,
			data: "0x1234",
		},
	],
})

Check the receipt

Wait for the receipt, check success, and read actualGasCost to see what you paid.

import { formatEther } from "viem"
 
const receipt = await smartAccountClient.waitForUserOperationReceipt({ hash: userOpHash })
 
if (!receipt.success) {
	throw new Error(`User operation reverted: ${receipt.reason ?? "unknown reason"}`)
}
 
console.log(`Included: ${chain.blockExplorers.default.url}/tx/${receipt.receipt.transactionHash}`)
console.log(`Gas you sponsored: ${formatEther(receipt.actualGasCost)} ${chain.nativeCurrency.symbol}`)

Sponsor an existing EOA with EIP-7702

To sponsor gas for a user's existing EOA without moving their funds, replace toSimpleSmartAccount with to7702SimpleSmartAccount, use EntryPoint 0.8, and attach a signed authorization to the user's first user operation. Follow the EIP-7702 quickstart. Check the chain has EIP-7702 support on the supported chains page; Ethereum, Base, Arbitrum, Optimism and Polygon do.

Test on a testnet first

To test without spending real funds, import a testnet such as sepolia or baseSepolia as chain. Sponsorship on testnets is free.

Let users pay gas in USDC instead

To charge users instead of sponsoring them, have them pay gas in USDC with the ERC-20 Paymaster. Follow How to send USDC without ETH for gas, and check your chain's USDC is listed in the supported tokens.

Limit what you sponsor (optional)

To cap your spend, for example per user or per user operation, create a policy in the sponsorship policies dashboard and pass its ID in the paymasterContext. User operations outside the policy's limits are declined, and sendUserOperation throws.

const SPONSORSHIP_POLICY_ID = "sp_your_policy_id"
 
const limitedHash = await smartAccountClient.sendUserOperation({
	calls: [{ to: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", value: 0n, data: "0x1234" }],
	paymasterContext: { sponsorshipPolicyId: SPONSORSHIP_POLICY_ID },
})

To sponsor first and charge USDC once a user hits a limit, see How to send USDC remittances without ETH for gas. For all policy options, see How to use sponsorship policies.

Related