This guide covers end-to-end Paymaster v2 integration for gas sponsorship (users pay nothing) and token-fee mode (users pay in ERC-20). Related pages in this section:

How billing works

  • For both mode: "sponsor" and mode: "token", usage deducts from the developer’s USD gas credit.
  • In mode: "token", token deductions are transferred to the configured paymaster treasury wallet as compensation.
  • constantFee is an extra token fee charged from the end user per transaction/userOp.

Dashboard-managed paymaster config (new flow)

Paymaster v2 now reads token-mode policy from your paymaster config (validated by validation-service), not from per-request context fields. Configure these in dashboard create/update paymaster:
  • isERC20
  • isSponsored
  • treasuryWalletAddress (required when isERC20=true)
  • constantFee (required when isERC20=true, 0..10)
  • validUntilMinutes
  • restrictionType: NONE | ALLOWLIST | BLOCKLIST
  • restrictedWallets (used for allowlist/blocklist checks)

Step 1: Create a Paymaster client (v2)

Use the API key you copied from the dashboard.
import { http } from "viem";
import { createPaymasterClient } from "viem/account-abstraction";

const CHAIN_ID = 80002; // example: base-amoy / your chain id
const API_KEY = "<YOUR_PAYMASTER_API_KEY>";

const paymasterClient = createPaymasterClient({
  transport: http(
    `https://paymaster.abstraxn.com/api/v2/${CHAIN_ID}/?apikey=${API_KEY}`,
  ),
});

Step 2: Call sendUserOperation (paymaster + paymasterContext)

sendUserOperation is where you pass:
  • paymaster: paymasterClient
  • paymasterContext (controls how v2 pays fees)
const hash = await bundlerClient.sendUserOperation({
  account: kernelAccount,  // your Smart Account
  calls: [
    // your call(s) live here
  ],
  paymaster: paymasterClient,
  paymasterContext: {
    // mode goes here (sponsor or token)
  },
});

Step 3: Sponsor mode (native gas sponsorship)

Use mode: "sponsor" when you want the paymaster to sponsor gas using the native gas flow.
paymasterContext: {
  mode: "sponsor",
},

Step 4: ERC20 (token) mode

Use mode: "token" when you want fees paid in an ERC20 token.

Required paymasterContext fields

For mode: "token", you must provide:
  • tokenAddress: ERC20 contract address used for fee payment
treasury and constantFee are sourced from paymaster config in the dashboard (via validation-service). Do not send these in request context.

paymasterContext field definitions (focused)

mode

Defines how gas is paid:
  • "sponsor": native gas sponsorship by paymaster
  • "token": ERC20 token is deducted as fee payment
paymasterContext: {
  mode: "sponsor", // or "token"
}

tokenAddress (required in mode: "token")

The ERC-20 token address to use for payment.
paymasterContext: {
  mode: "token",
  tokenAddress: "0xTOKEN_ADDRESS",
}

treasury (dashboard-configured)

The receiver wallet for deducted ERC20 fees (compensation wallet).
paymasterContext: {
  mode: "token",
  // treasury is read from dashboard paymaster configuration
}

constantFee (dashboard-configured)

Additional token amount charged per transaction / userOp.
paymasterContext: {
  mode: "token",
  // constantFee is read from dashboard paymaster configuration
}

Example paymasterContext (token mode)

paymasterContext: {
  mode: "token",
  tokenAddress: TOKEN_ADDRESS,
  // treasury and constantFee are read from paymaster configuration
},

ERC20 approval: what you must do

In ERC20 token mode, the paymaster deducts fees during the same UserOperation (post-op). That deduction requires the sender to have ERC20 allowance for the paymaster contract. So you must add an approve(...) call into calls before your real action call. Minimal pattern:
import { encodeFunctionData, erc20Abi, maxUint256 } from "viem";

const approveData = encodeFunctionData({
  abi: erc20Abi,
  functionName: "approve",
  args: [PAYMASTER_ADDRESS, maxUint256], // or approve the exact needed amount
});

const hash = await bundlerClient.sendUserOperation({
  account: kernelAccount,
  calls: [
    // 1) give allowance to paymaster
    { to: TOKEN_ADDRESS, value: 0n, data: approveData },
    // 2) your real action call(s)
    { to: TARGET_CONTRACT, value: 0n, data: TARGET_CALLDATA },
  ],
  paymaster: paymasterClient,
  paymasterContext: {
    mode: "token",
    tokenAddress: TOKEN_ADDRESS,
    // treasury and constantFee are fetched from paymaster configuration
  },
});

Notes you must know

  1. PAYMASTER_ADDRESS must be the Paymaster contract address used by your backend for the chain you are using.
  2. If token-mode configuration is missing in dashboard (isERC20, treasuryWalletAddress, constantFee), v2 will reject the request.