Gasless Transactions on Base using Base Paymaster
Still trying to onboard users to your app? Want to break free from the worries of gas transactions and sponsor them for your users on Base? Look no further!
Base transaction fees are typically less than a penny, but the concept of gas can still be confusing for new users. You can abstract this away and improve your UX by using the Base Paymaster. The Paymaster allows you to:
- Batch multi-step transactions
- Create custom gasless experiences
- Sponsor up to $10k monthly on mainnet (unlimited on testnet)
Note: If you need an increase in your sponsorship limit, please reach out on Discord!
Objectives
- Configure security measures to ensure safe and reliable transactions.
- Manage and allocate resources for sponsored transactions.
- Subsidize transaction fees for users, enhancing the user experience by making transactions free.
- Set up and manage sponsored transactions on various schedules, including weekly, monthly, and daily cadences.
Prerequisites
This tutorial assumes you have:
-
A Coinbase Cloud Developer Platform Account
If not, sign up on the CDP site. Once you have your account, you can manage projects and utilize tools like the Paymaster. -
Familiarity with Smart Accounts and ERC 4337
Smart Accounts are the backbone of advanced transaction patterns (e.g., bundling, sponsorship). If you’re new to ERC 4337, check out external resources like the official EIP-4337 explainer before starting. -
Foundry
Foundry is a development environment, testing framework, and smart contract toolkit for Ethereum. You’ll need it installed locally for generating key pairs and interacting with smart contracts.
Testnet vs. Mainnet
If you prefer not to spend real funds, you can switch to Base Goerli (testnet). The steps below are conceptually the same. Just select Base Goerli in the Coinbase Developer Platform instead of Base Mainnet, and use a contract deployed on Base testnet for your allowlisted methods.
Set Up a Base Paymaster & Bundler
In this section, you will configure a Paymaster to sponsor payments on behalf of a specific smart contract for a specified amount.
- Navigate to the Coinbase Developer Platform.
- Create or select your project from the upper left corner of the screen.
- Click on the Paymaster tool from the left navigation.
- Go to the Configuration tab and copy the RPC URL to your clipboard — you’ll need this shortly in your code.
Screenshots
-
Selecting your project
-
Navigating to the Paymaster tool
-
Configuration screen
Allowlist a Sponsorable Contract
- From the Configuration page, ensure Base Mainnet (or Base Goerli if you’re testing) is selected.
- Enable your paymaster by clicking the toggle button.
- Click Add to add an allowlisted contract.
- For this example, add
0x83bd615eb93eE1336acA53e185b03B54fF4A17e8
, and add the functionmintTo(address)
.
Use your own contract
We use a simple NFT contract on Base mainnet as an example. Feel free to substitute your own.
Global & Per User Limits
Scroll down to the Per User Limit section. You can set:
- Dollar amount limit or number of UserOperations per user
- Limit cycles that reset daily, weekly, or monthly
For example, you might set:
max USD
to$0.05
max UserOperation
to1
This means each user can only have $0.05 in sponsored gas and 1 user operation before the cycle resets.
Limit Cycles
These reset based on the selected cadence (daily, weekly, monthly).
Next, Set the Global Limit. For example, set this to $0.07
so that once the entire paymaster has sponsored $0.07 worth of gas (across all users), no more sponsorship occurs unless you raise the limit.
Test Your Paymaster Policy
Now let’s verify that these policies work. We’ll:
- Create two local key pairs (or use private keys you own).
- Generate two Smart Accounts.
- Attempt to sponsor multiple transactions to see your policy in action.
Installing Foundry
- Ensure you have Rust installed. If not:
Terminal
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
- Install Foundry:
Terminal
curl -L https://foundry.paradigm.xyz | bash foundryup
- Verify it works:
If you see Foundry usage info, you’re good to go!Terminal
cast --help
Create Your Project & Generate Key Pairs
- Make a new folder and install dependencies:
Terminal
mkdir sponsored_transactions cd sponsored_transactions npm init es6 npm install permissionless npm install viem touch index.js
- Generate two key pairs with Foundry:
You’ll see something like:Terminal
cast wallet new cast wallet new
Store these private keys somewhere safe — ideally in aTerminalSuccessfully created new keypair. Address: 0xD440D746... Private key: 0x01c9720c1dfa3c9...
.env
file.
Project Structure With Environment Variables
Create a .env
file in sponsored_transactions
:
PAYMASTER_RPC_URL=https://api.developer.coinbase.com/rpc/v1/base/<SPECIAL-KEY>
PRIVATE_KEY_1=0x01c9720c1dfa3c9...
PRIVATE_KEY_2=0xbcd6fbc1dfa3c9...
Security
Never commit.env
files to a public repo!
Example index.js
(Using Twoslash)
Below is a full example of how you might structure index.js
.
We’ll use twoslash code blocks (````js twoslash`) to highlight key lines and explanations.
// --- index.js ---
// @noErrors
// 1. Import modules and environment variables
import 'dotenv/config';
import { http, createPublicClient, encodeFunctionData } from 'viem';
import { base } from 'viem/chains';
import { createSmartAccountClient } from 'permissionless';
import { privateKeyToSimpleSmartAccount } from 'permissionless/accounts';
import { createPimlicoPaymasterClient } from 'permissionless/clients/pimlico';
// 2. Retrieve secrets from .env
// Highlight: environment variables for paymaster, private keys
const rpcUrl = process.env.PAYMASTER_RPC_URL; // highlight
const firstPrivateKey = process.env.PRIVATE_KEY_1; // highlight
const secondPrivateKey = process.env.PRIVATE_KEY_2; // highlight
// 3. Declare Base addresses (entrypoint & factory)
const baseEntryPoint = '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789';
const baseFactoryAddress = '0x15Ba39375ee2Ab563E8873C8390be6f2E2F50232';
// 4. Create a public client for Base
const publicClient = createPublicClient({
chain: base,
transport: http(rpcUrl),
});
// 5. Setup Paymaster client
const cloudPaymaster = createPimlicoPaymasterClient({
chain: base,
transport: http(rpcUrl),
entryPoint: baseEntryPoint,
});
// 6. Create Smart Accounts from the private keys
async function initSmartAccounts() {
const simpleAccount = await privateKeyToSimpleSmartAccount(publicClient, {
privateKey: firstPrivateKey,
factoryAddress: baseFactoryAddress,
entryPoint: baseEntryPoint,
});
const simpleAccount2 = await privateKeyToSimpleSmartAccount(publicClient, {
privateKey: secondPrivateKey,
factoryAddress: baseFactoryAddress,
entryPoint: baseEntryPoint,
});
// 7. Create SmartAccountClient for each
const smartAccountClient = createSmartAccountClient({
account: simpleAccount,
chain: base,
bundlerTransport: http(rpcUrl),
middleware: {
sponsorUserOperation: cloudPaymaster.sponsorUserOperation,
},
});
const smartAccountClient2 = createSmartAccountClient({
account: simpleAccount2,
chain: base,
bundlerTransport: http(rpcUrl),
middleware: {
sponsorUserOperation: cloudPaymaster.sponsorUserOperation,
},
});
return { smartAccountClient, smartAccountClient2 };
}
// 8. ABI for the NFT contract
const nftAbi = [
// ...
// truncated for brevity
];
// 9. Example function to send a transaction from a given SmartAccountClient
async function sendTransaction(client, recipientAddress) {
try {
// encode the "mintTo" function call
const callData = encodeFunctionData({
abi: nftAbi,
functionName: 'mintTo',
args: [recipientAddress], // highlight: specify who gets the minted NFT
});
const txHash = await client.sendTransaction({
account: client.account,
to: '0x83bd615eb93eE1336acA53e185b03B54fF4A17e8', // address of the NFT contract
data: callData,
value: 0n,
});
console.log(`✅ Transaction successfully sponsored for ${client.account.address}`);
console.log(`🔍 View on BaseScan: https://basescan.org/tx/${txHash}`);
} catch (error) {
console.error('Transaction failed:', error);
}
}
// 10. Main flow: init accounts, send transactions
(async () => {
const { smartAccountClient, smartAccountClient2 } = await initSmartAccounts();
// Send a transaction from the first account
await sendTransaction(smartAccountClient, smartAccountClient.account.address);
// Send a transaction from the second account
// For variety, let’s also mint to the second account's own address
await sendTransaction(smartAccountClient2, smartAccountClient2.account.address);
})();
Note:
- Run this via
node index.js
from your project root.- If your Paymaster settings are strict (e.g., limit 1 transaction per user), the second time you run the script, you may get a “request denied” error, indicating the policy is working.
Hitting Policy Limits & Troubleshooting
-
Per-User Limit
If you see an error like:{ "code": -32001, "message": "request denied - rejected due to maximum per address transaction count reached" }
That means you’ve hit your UserOperation limit for a single account. Return to the Coinbase Developer Platform UI to adjust the policy.
-
Global Limit
If you repeatedly run transactions and eventually see:{ "code": -32001, "message": "request denied - rejected due to max global usd spend limit reached" }
You’ve hit the global limit of sponsored gas. Increase it in the CDP dashboard and wait a few minutes for changes to take effect.
Verifying Token Ownership (Optional)
Want to confirm the token actually minted? You can read the NFT’s balanceOf
function:
import { readContract } from 'viem'; // highlight
// example function
async function checkNftBalance(publicClient, contractAddress, abi, ownerAddress) {
const balance = await publicClient.readContract({
address: contractAddress,
abi,
functionName: 'balanceOf',
args: [ownerAddress],
});
console.log(`NFT balance of ${ownerAddress} is now: ${balance}`);
}
Conclusion
In this tutorial, you:
- Set up and configured a Base Paymaster on the Coinbase Developer Platform.
- Allowlisted a contract and specific function (
mintTo
) for sponsorship. - Established per-user and global sponsorship limits to control costs.
- Demonstrated the sponsorship flow with Smart Accounts using
permissionless
,viem
, and Foundry-generated private keys.
This approach can greatly improve your dApp’s user experience by removing gas friction. For more complex sponsorship schemes (like daily or weekly cycles), simply tweak your per-user and global limit settings in the Coinbase Developer Platform.
Next Steps
- Use a proxy service for better endpoint security.
- Deploy your own contracts and allowlist them.
- Experiment with bundling multiple calls into a single sponsored transaction.