Skip to content

Sponsor Gas

In the Quickstart, we covered an example that sends a user operation with gas sponsorship using our Gas Manager. In this guide, we'll recap how to sponsor gas using the Gas Manager, and also show you how to use other paymaster providers as well.

Sponsor Gas with Gas Manager

1. Create gas policy

A gas manager policy is a set of rules that define which UOs are eligible for gas sponsorship. You can control which operations are eligible for sponsorship by defining rules:

  • Spending rules: limit the amount of money or the number of user ops that can be sponsored by this policy
  • Allowlist: restrict wallet addresses that are eligible for sponsorship. The policy will only sponsor gas for UOs that were sent by addresses on this list.
  • Blocklist: ban certain addresses from receiving sponsorship under this policy
  • Policy duration: define the duration of your policy and the sponsorship expiry period. This is the period for which the Gas Manager signature (paymaster data) will remain valid once it is generated.

To learn more about policy configuration, refer to the guide on setting up a gas manager policy.

Once you have decided on policy rules for your app, create a policy in the Gas Manager dashboard.

Now you should have a Gas policy created with a policy id you can use to sponsor gas for your users.

Policy ID

2. Create a Smart Account Client

Now you can create a Smart Account Client which is configured to sponsor gas.

import { createAlchemySmartAccountClient, sepolia } from "@account-kit/infra";
import { createLightAccount } from "@account-kit/smart-contracts";
// You can replace this with any signer you'd like
// We're using a LocalAccountSigner to generate a local key to sign with
import { LocalAccountSigner } from "@aa-sdk/core";
import { http } from "viem";
import { generatePrivateKey } from "viem/accounts";
 
export const client = createAlchemySmartAccountClient({
  apiKey: "YOUR_API_KEY",
  policyId: "YOUR_POLICY_ID",
  chain: sepolia,
  account: await createLightAccount({
    chain: sepolia,
    transport: http(`${sepolia.rpcUrls.alchemy.http[0]}/YOUR_API_KEY`),
    signer: LocalAccountSigner.privateKeyToAccountSigner(generatePrivateKey()),
  }),
});

3. Send a user operation with gas sponsorship

example.ts
import { client } from "./client";
 
const { hash } = await client.sendUserOperation({
  uo: {
    target: "0xTARGET_ADDRESS",
    data: "0x",
    value: 0n,
  },
});

Sponsor Gas with 3rd-party paymaster

If you're using a 3rd party paymaster to sponsor gas, but our Bundler RPC endpoints, the set up is a bit more involved, but easily doable.

Prerequisite: install aa-sdk/core

Because the Smart Account Client exported from @account-kit/infra assumes you're using our infra for both gas sponsorship and bundler RPCs, you'll need to install @aa-sdk/core so you can create more configurable clients.

yarn
yarn add @aa-sdk/core

ERC-7677 compliant paymasters

If your paymaster is ERC-7677 compliant, we export a paymaster middleware that makes it really easy to integrate with that paymaster.

import {
  createSmartAccountClient,
  erc7677Middleware,
  split,
} from "@aa-sdk/core";
import {
  sepolia,
  alchemyFeeEstimator,
  createAlchemyPublicRpcClient,
} from "@account-kit/infra";
import { createLightAccount } from "@account-kit/smart-contracts";
// You can replace this with any signer you'd like
// We're using a LocalAccountSigner to generate a local key to sign with
import { LocalAccountSigner } from "@aa-sdk/core";
import { http, custom } from "viem";
import { generatePrivateKey } from "viem/accounts";
 
// 1. create an alchemy rpc client
const alchemyRpcClient = createAlchemyPublicRpcClient({
  chain: sepolia,
  connectionConfig: {
    apiKey: "API_KEY",
  },
});
 
// 2. create a split transport to route traffic between the paymaster and the bundler
const transport = split({
  overrides: [
    {
      methods: ["pm_getPaymasterStubData", "pm_getPaymasterData"],
      transport: http("PAYMASTER_URL"),
    },
  ],
  fallback: custom(alchemyRpcClient),
});
 
// 3. create smart account client
export const client = createSmartAccountClient({
  transport,
  chain: sepolia,
  account: await createLightAccount({
    chain: sepolia,
    transport: http(`${sepolia.rpcUrls.alchemy.http[0]}/YOUR_API_KEY`),
    signer: LocalAccountSigner.privateKeyToAccountSigner(generatePrivateKey()),
  }),
  // this is required to get correct fee estimates when using our Bundler RPC
  feeEstimator: alchemyFeeEstimator(alchemyRpcClient),
  ...erc7677Middleware(),
});

Other 3rd Party Paymasters

If your paymaster provider is not ERC-7677 compliant, the setup is very similar, but you'll need to provide your own paymasterAndData and dummyPaymasterAndData middleware overrides to handle the paymaster calls. In this example, we'll show you how to use Stackup to provide ERC-20 gas payments.

import {
  createSmartAccountClient,
  split,
  deepHexlify,
  resolveProperties,
} from "@aa-sdk/core";
import {
  sepolia,
  alchemyFeeEstimator,
  createAlchemyPublicRpcClient,
} from "@account-kit/infra";
import { createLightAccount } from "@account-kit/smart-contracts";
// You can replace this with any signer you'd like
// We're using a LocalAccountSigner to generate a local key to sign with
import { LocalAccountSigner } from "@aa-sdk/core";
import { http, custom } from "viem";
import { generatePrivateKey } from "viem/accounts";
 
// 1. create an alchemy rpc client
const alchemyRpcClient = createAlchemyPublicRpcClient({
  chain: sepolia,
  connectionConfig: {
    apiKey: "API_KEY",
  },
});
 
// 2. create a split transport to route traffic between the paymaster and the bundler
const transport = split({
  overrides: [
    {
      methods: ["pm_sponsorUserOperation"],
      // 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"),
    },
  ],
  fallback: custom(alchemyRpcClient),
});
 
// 3. create smart account client
export const client = createSmartAccountClient({
  transport,
  chain: sepolia,
  account: await createLightAccount({
    chain: sepolia,
    transport: custom(alchemyRpcClient),
    signer: LocalAccountSigner.privateKeyToAccountSigner(generatePrivateKey()),
  }),
  // Bypasses alchemy gas estimation and instead uses Stackup for gas estimation
  // If your paymaster doesn't provide gas estimation, then don't replace the gasEstimator
  gasEstimator: async (userOp) => ({
    ...userOp,
    callGasLimit: "0x0",
    preVerificationGas: "0x0",
    verificationGasLimit: "0x0",
  }),
  // this is required to get correct fee estimates when using our Bundler RPC
  feeEstimator: alchemyFeeEstimator(alchemyRpcClient),
  dummyPaymasterAndData: async (userop) => ({
    ...userop,
    paymasterAndData: "0x",
  }),
  paymasterAndData: async (userop, { client, account }) => {
    const pmResponse: any = await client.request({
      // @ts-ignore
      method: "pm_sponsorUserOperation",
      params: [
        deepHexlify(await resolveProperties(userop)),
        account.getEntryPoint().address,
        {
          // @ts-ignore
          type: "payg", // Replace with ERC20 context based on stackups documentation
        },
      ],
    });
    return {
      ...userop,
      ...pmResponse,
    };
  },
});