Skip to content

Using a third-party paymaster

The SmartAccountClient within @aa-sdk/core is unopinionated about which paymaster you use, so you can connect to any paymaster really simply. Configuration is done using the paymasterAndData config option when you call createSmartAccountClient.

Usage

import { createSmartAccountClient } from "@aa-sdk/core";
import { http } from "viem";
import { sepolia } from "viem/chains";
 
const chain = sepolia;
const client = createSmartAccountClient({
  chain,
  transport: http("RPC_URL"),
  // sets the dummy paymasterAndData with paymaster address appended with some dummy paymasterData
  // that looks like a valid paymasterData
  dummyPaymasterAndData: async (userop) => ({
    ...userop,
    paymasterAndData: `0x<PAYMASTER_ADDRESS><PAYMASTER_DUMMY_DATA>`,
  }),
  paymasterAndData: async (userop, opts) => {
    // call your paymaster here to sponsor the userop
    // leverage the `opts` field to apply any overrides
    return {
      ...userop,
      paymasterAndData: "0xresponsefromprovider",
    };
  },
});

ERC-7677 Paymaster

If your pamyaster supports the ERC-7677 standard, you can use the erc7677Middleware to interact with it.

Usage with single RPC provider

If you're using the same RPC provider for your Paymaster, Bundler, and Node RPC traffic, then you can do the following:

import { createSmartAccountClient, erc7677Middleware } from "@aa-sdk/core";
import { http } from "viem";
import { sepolia } from "viem/chains";
 
const chain = sepolia;
const client = createSmartAccountClient({
  chain,
  // This example assumes that your RPC provider supports the ERC-7677 methods
  transport: http("RPC_URL"),
  ...erc7677Middleware(),
});

Usage with multiple RPC providers

If you're using a separate RPC provider for your Paymaster, you can can use the (split)[/third-party/bundlers#splitting-bundler-traffic-and-node-rpc-traffic] transport to route your ERC-7677 traffic to a different provider:

import {
  createSmartAccountClient,
  erc7677Middleware,
  split,
} from "@aa-sdk/core";
import { http } from "viem";
import { sepolia } from "viem/chains";
 
const chain = sepolia;
const erc7677Methods = ["pm_getPaymasterStubData", "pm_getPaymasterData"];
const transport = split({
  overrides: [
    // NOTE: if you're splitting Node and Bundler traffic too, you can add the bundler config to this array
    {
      methods: erc7677Methods,
      transport: http("PAYMASTER_RPC_URL"),
    },
  ],
  fallback: http("NODE_AND_BUNDLER_RPC_URL"),
});
 
const client = createSmartAccountClient({
  chain,
  transport,
  ...erc7677Middleware(),
});

ERC20 Gas Sponsorship

We are working on building support for an ERC20 paymaster!

In the meantime, you could use a third-party paymaster, such as Stackup, to sponsor ERC-20 gas. Here's an example using Stackup with the Alchemy SDK:

import { createMultiOwnerModularAccountClient } from "@account-kit/smart-contracts";
import {
  alchemyFeeEstimator,
  createAlchemyPublicRpcClient,
  alchemy,
} from "@account-kit/infra";
import {
  deepHexlify,
  resolveProperties,
  LocalAccountSigner,
} from "@aa-sdk/core";
import { createClient, http } from "viem";
import { sepolia } from "viem/chains";
 
const signer = LocalAccountSigner.generatePrivateKeySigner();
const chain = sepolia;
const stackupClient = createClient({
  // TODO: Replace with your stackup API key here (https://docs.stackup.sh/docs/paymaster-api)
  transport: http("https://api.stackup.sh/v1/paymaster/STACKUP_API_KEY"),
});
 
const alchemyTransport = alchemy({
  // TODO: Replace with your Alchemy API key (https://dashboard.alchemypreview.com/apps)
  apiKey: "ALCHEMY_API_KEY",
});
 
const alchemyClient = await createMultiOwnerModularAccountClient({
  chain,
  signer,
  transport: alchemyTransport,
  // Bypasses alchemy gas estimation and instead uses Stackup for gas estimation
  gasEstimator: async (userOp) => ({
    ...userOp,
    callGasLimit: "0x0",
    preVerificationGas: "0x0",
    verificationGasLimit: "0x0",
  }),
  // Uses alchemy fee estimation to comply with bundler
  feeEstimator: alchemyFeeEstimator(alchemyTransport),
  paymasterAndData: async (userop, opts) => {
    const pmResponse: any = await stackupClient.request({
      // @ts-ignore
      method: "pm_sponsorUserOperation",
      params: [
        deepHexlify(await resolveProperties(userop)),
        opts.account.getEntryPoint().address,
        {
          // @ts-ignore
          type: "payg", // Replace with ERC20 context based on stackups documentation
        },
      ],
    });
    return {
      ...userop,
      ...pmResponse,
    };
  },
});