# Guide to Paying Gas for Users with Base Paymaster

This guide shows you how to sponsor transaction fees for users with Base Paymaster, making your dApp more user-friendly.

When interacting with a blockchain, users are required to pay transaction (gas) fees. While these fees are typically low on Base, often less than $0.01, they can still be confusing or intimidating, especially for non-Web3-native users. Requiring users to fund a wallet before engaging with your app can create friction and negatively impact the user experience (UX). Paymaster allows you to sponsor up to $15k monthly on mainnet (unlimited on testnet). Get Gas Fees Covered. [Apply Now](https://forms.gle/7ggCftfeNsJuvwUJA)

### **Prerequisites**

To follow this tutorial, you should already have:

1. **A Coinbase Cloud Developer Platform (CDP) Account**
    
    If you have not, sign up on the [CDP Portal](https://portal.cdp.coinbase.com). After signing up, you can manage projects and utilize different tools like the paymaster.
    
2. **A basic understanding of Smart Accounts and how ERC-4337 works**
    
    Smart Accounts are the foundation of advanced transaction flows like gas sponsorship and transaction batching. This tutorial builds on the ERC-4337 account abstraction standard, so if you’re unfamiliar with it, we recommend checking out resources like the official EIP-4337 explainer [before getting started.](https://eips.ethereum.org/EIPS/eip-4337)
    
3. **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.
    

### Set Up Gas Sponsorship with Base Paymaster

This section walks you through setting up gas sponsorship using Base Paymaster. You’ll configure your project to sponsor transactions on behalf of users by interacting with the Paymaster and bundler services provided by the Coinbase Developer Platform.

1. Go to the [Coinbase Developer Platform](https://portal.cdp.coinbase.com/).
    
2. Create or select your project from the upper-left corner.
    
3. From the left-hand menu, hover on “Base Tools” and click “Paymaster”
    
4. Navigate to the configuration tab and copy the RPC\_URL, you’ll use it in your code to connect to the bundler and paymaster Services.
    

### **Screenshots**

1. Selecting your project
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1751803878761/7406849b-9d65-4602-80a7-57cf2128ad71.png align="center")
    
2. Navigate to base tools &gt; paymaster
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1751804039489/edd08ca1-010c-4eed-8a37-3d8bdf296e07.png align="center")
    
3. Configuration screen
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1751804256823/21fafade-bb7c-4704-895c-56fe7da1a0ff.png align="center")
    

### Allowlist a Contract for Sponsorship

1. On the configuration page, make sure Base Mainnet is selected. Select Base Sepolia if you’re testing (testnet)
    
2. Turn on your Paymaster by toggling the ***Enable Paymaster*** switch
    
3. Click “Add” to allowlist a contract that can receive sponsored transactions
    
4. For this example, use the
    
    contract name: `DemoNFT`
    
    contract address:`0xd33bb1C4c438600db4611bD144fDF5048f6C8E44`  
    Then add the function you want to sponsor: `mintTo(address)`.
    

> This ensures your Paymaster only covers gas fees for specific functions you approve — in this case, the `mintTo` function on the `DemoNFT` contract.  
> **Note:** If you leave the function input box empty, the Paymaster will sponsor **all functions** on the contract, so use this with caution.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1751804908911/af34d485-5285-4886-9ba0-e0a8771b5011.png align="center")

> Note: The contract name, address, and function above are from our example NFT contract on Base Mainnet. You should replace them with the details of your own contract.

### Global & Per User Limits

Scroll to **Per User Limit** to set:

* Max USD (e.g., $0.05)
    
* Max UserOperations (e.g., 1)
    
* Reset cycle (daily, weekly, or monthly)
    

This means each user gets $0.05 and 1 sponsored transaction per cycle.

Then set the **Global Limit** (e.g., $0.07). Once reached, no more sponsorships happen until the limit is increased.

### Implementing Gas Sponsorship on the Backend

**Installing** **Foundry**

1. Ensure you have Rust installed
    
    ```plaintext
    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 
    ```
    
2. Install Foundry
    
    ```plaintext
    curl -L https://foundry.paradigm.xyz | bash
    foundryup 
    ```
    
3. Verify it works
    
    ```plaintext
    cast --help
    ```
    
    You should see Foundry usage information **voilà**, you’re good to go!
    

### Create Your Project and Generate Key Pairs

1. Create a folder and install dependencies, viem and dotenv:
    
    ```plaintext
    mkdir viem-gasless
    cd viem-gasless
    npm install viem
    npm install dotenv
    touch config.js
    touch index.js
    touch example-app-abi.js
    ```
    
2. Generate a key pair with foundry
    
    ```plaintext
    cast wallet new
    ```
    
3. You’ll see something like:
    
    ```plaintext
    Successfully created new keypair.
    Address:     0xa05Ed6858568cbc14cfEd559C068E02e95521De4
    Private key: 0xf920002d619ca04287848a9...
    ```
    
    **Store these private keys safe**
    

Create a `.env` file in the `viem-gasless` folder. In the `.env`, you’ll add the rpcURL for your paymaster and the private key for your account. The rpcURL can be found from the CDP Portal, it follows the pattern [`https://api.developer.coinbase.com/rpc/v1/base/<SPECIAL-KEY>`](https://api.developer.coinbase.com/rpc/v1/base/<SPECIAL-KEY>Navigate). That way our `.env` would look like this:

```plaintext
RPC_URL=https://api.developer.coinbase.com/rpc/v1/base/<SPECIAL-KEY>
OWNER_PRIVATE_KEY=0xf920002d619ca04287848a9...
```

> `.env` should not be committed to a public repo. Do not forget to add it to `.gitignore`

### Example `config.js`

```javascript
import { createPublicClient, http } from "viem";
import { toCoinbaseSmartAccount } from "viem/account-abstraction";
import { privateKeyToAccount } from "viem/accounts";
import { base } from "viem/chains";
import dotenv from 'dotenv';

// Load environment variables from .env file
dotenv.config();

const RPC_URL = process.env.RPC_URL;
const OWNER_PRIVATE_KEY = process.env.OWNER_PRIVATE_KEY;

if (!RPC_URL) {
    throw new Error('RPC_URL is not set in environment variables');
}

if (!OWNER_PRIVATE_KEY) {
    throw new Error('OWNER_PRIVATE_KEY is not set in environment variables');
}

export { RPC_URL };

export const client = createPublicClient({
    chain: base,
    transport: http(RPC_URL),
});

const owner = privateKeyToAccount(OWNER_PRIVATE_KEY);

export const account = await toCoinbaseSmartAccount({
    client,
    owners: [owner],
});
```

`example-app-abi-.js`  

```javascript
export const abi = [
  {
    inputs: [
      {
        internalType: "address",
        name: "to",
        type: "address",
      },
    ],
    name: "mintTo",
    outputs: [
      {
        internalType: "uint256",
        name: "",
        type: "uint256",
      },
    ],
    stateMutability: "nonpayable",
    type: "function",
  },
  {
    inputs: [
      {
        internalType: "address",
        name: "owner",
        type: "address",
      },
    ],
    name: "balanceOf",
    outputs: [
      {
        internalType: "uint256",
        name: "",
        type: "uint256",
      },
    ],
    stateMutability: "view",
    type: "function",
  },
];
```

`index.js`

```javascript
import { http } from "viem";
import { base } from "viem/chains";
import { createBundlerClient } from "viem/account-abstraction";
import { account, client, RPC_URL } from "./config.js";
import { abi } from "./example-app-abi.js";

console.log("🚀 Starting gasless NFT minting process...");

const bundlerClient = createBundlerClient({
  account,
  client,
  transport: http(RPC_URL),
  chain: base,
});

const nftContractAddress = "0xd33bb1C4c438600db4611bD144fDF5048f6C8E44"; // DEMO NFT Contract Deployed on Mainnet (base)

const mintTo = {
  abi: abi,
  functionName: "mintTo",
  to: nftContractAddress,
  args: [account.address],
};
const calls = [mintTo];

account.userOperation = {
  estimateGas: async (userOperation) => {
    const estimate = await bundlerClient.estimateUserOperationGas(
      userOperation
    );
    estimate.preVerificationGas = estimate.preVerificationGas * 2n;
    return estimate;
  },
};

try {
  console.log("📤 Sending user operation...");
  
  const userOpHash = await bundlerClient.sendUserOperation({
    account,
    calls,
    paymaster: true,
  });

  console.log("⏳ Waiting for transaction receipt...");

  const receipt = await bundlerClient.waitForUserOperationReceipt({
    hash: userOpHash,
  });

  console.log("✅ Transaction successfully sponsored");
  console.log(
    `⛽ View sponsored UserOperation on blockscout: https://base.blockscout.com/op/${receipt.userOpHash}`
  );
  process.exit(0);
} catch (error) {
  console.error("❌ Error sending transaction: ", error);
  process.exit(1);
}
```

Now that the code is implemented, lets run it: Run this via `node index.js` from your project root.

```plaintext
node index.js
```

To test if your spend policy is working, set it to allow just **1 transaction per user**. When you run the script again, you should get a **"request denied"** error — that means it’s working!

Want to see full examples? We’ve got setups for **Wagmi + Vite**, **Wagmi + Next.js**, and even **server-side integration**.  
Check them out on [GitHub](https://github.com/Base-West-Africa/Demo-examples)
