Learn how to leverage the Base Paymaster for seamless, gasless transactions on the Coinbase Cloud Developer Platform.
Base transaction fees are typically less than a penny, but the concept of gas can still be confusing for new users and lead to poor user experience when users don’t have gas funds in their wallet. 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 $15k monthly on mainnet (unlimited on testnet)
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 Sepolia (testnet). The steps below are conceptually the same. Just select Base Sepolia in the Coinbase Developer Platform instead of Base Mainnet, and use a contract deployed on Base testnet for your allowlisted methods.
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 to 1
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.
Create a .env file in the sponsored_transactions directory. In the .env, you’ll add the rpcURL for your paymaster and the private keys for your accounts:
[Find your Paymaster & Bundler endpoint]
The Paymaster & Bundler endpoint is the URL for your Coinbase Developer Platform (CDP) Paymaster.
This was saved in the previous section and follows this format: https://api.developer.coinbase.com/rpc/v1/base/<SPECIAL-KEY>
Navigate to the Paymaster Tool and select the Configuration tab at the top of the screen to obtain your RPC URL.
[Secure your endpoints]
You will create a constant for our Paymaster & Bundler endpoint obtained from cdp.portal.coinbase.com. The most secure way to do this is by using a proxy. For the purposes of this demo, hardcode it into our index.js file. For product, we highly recommend using a proxy service.
Below is a full example of how you might structure index.js.
twoslash
Copy
Ask AI
// --- index.js ---// @noErrors// 1. Import modules and environment variablesimport '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 keysconst rpcUrl = process.env.PAYMASTER_RPC_URL; // highlightconst firstPrivateKey = process.env.PRIVATE_KEY_1; // highlightconst 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 Baseconst publicClient = createPublicClient({ chain: base, transport: http(rpcUrl),});// 5. Setup Paymaster clientconst cloudPaymaster = createPimlicoPaymasterClient({ chain: base, transport: http(rpcUrl), entryPoint: baseEntryPoint,});// 6. Create Smart Accounts from the private keysasync 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 contractconst nftAbi = [ // ... // truncated for brevity];// 9. Example function to send a transaction from a given SmartAccountClientasync 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);})();
Now that the code is implemented, lets run it:
Run this via node index.js from your project root.
Copy
Ask AI
node index.js
You should see a “Transaction successfully sponsored” output.
To confirm that your spend policies are correctly in place, try running the script again. 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.
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 onchain app’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.