3rd Party Paymasters

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

1import { createSmartAccountClient } from "@aa-sdk/core";
2import { http } from "viem";
3import { sepolia } from "viem/chains";
4
5const chain = sepolia;
6const client = createSmartAccountClient({
7 chain,
8 transport: http("RPC_URL"),
9 // sets the dummy paymasterAndData with paymaster address appended with some dummy paymasterData
10 // that looks like a valid paymasterData
11 dummyPaymasterAndData: async (userop) => ({
12 ...userop,
13 paymasterAndData: `0x<PAYMASTER_ADDRESS><PAYMASTER_DUMMY_DATA>`,
14 }),
15 paymasterAndData: async (userop, opts) => {
16 // call your paymaster here to sponsor the userop
17 // leverage the `opts` field to apply any overrides
18 return {
19 ...userop,
20 paymasterAndData: "0xresponsefromprovider",
21 };
22 },
23});

ERC-7677 Paymaster

If your paymaster 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:

1import { createSmartAccountClient, erc7677Middleware } from "@aa-sdk/core";
2import { http } from "viem";
3import { sepolia } from "viem/chains";
4
5const chain = sepolia;
6const client = createSmartAccountClient({
7 chain,
8 // This example assumes that your RPC provider supports the ERC-7677 methods
9 transport: http("RPC_URL"),
10 ...erc7677Middleware(),
11});

Usage with multiple RPC providers

If you’re using a separate RPC provider for your Paymaster, you can can use the split transport to route your ERC-7677 traffic to a different provider:

1import {
2 createSmartAccountClient,
3 erc7677Middleware,
4 split,
5} from "@aa-sdk/core";
6import { http } from "viem";
7import { sepolia } from "viem/chains";
8
9const chain = sepolia;
10const erc7677Methods = ["pm_getPaymasterStubData", "pm_getPaymasterData"];
11const transport = split({
12 overrides: [
13 // NOTE: if you're splitting Node and Bundler traffic too, you can add the bundler config to this array
14 {
15 methods: erc7677Methods,
16 transport: http("PAYMASTER_RPC_URL"),
17 },
18 ],
19 fallback: http("NODE_AND_BUNDLER_RPC_URL"),
20});
21
22const client = createSmartAccountClient({
23 chain,
24 transport,
25 ...erc7677Middleware(),
26});

ERC-20 Gas Sponsorship

We are working on building support for an ERC-20 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:

1import { createMultiOwnerModularAccountClient } from "@account-kit/smart-contracts";
2import {
3 alchemyFeeEstimator,
4 createAlchemyPublicRpcClient,
5 alchemy,
6} from "@account-kit/infra";
7import {
8 deepHexlify,
9 resolveProperties,
10 LocalAccountSigner,
11} from "@aa-sdk/core";
12import { createClient, http } from "viem";
13import { sepolia } from "viem/chains";
14
15const signer = LocalAccountSigner.generatePrivateKeySigner();
16const chain = sepolia;
17const stackupClient = createClient({
18 // TODO: Replace with your stackup API key here (https://docs.stackup.sh/docs/paymaster-api)
19 transport: http("https://api.stackup.sh/v1/paymaster/STACKUP_API_KEY"),
20});
21
22const alchemyTransport = http("ALCHEMY_RPC_URL");
23
24const alchemyClient = await createMultiOwnerModularAccountClient({
25 chain,
26 signer,
27 transport: http("ALCHEMY_RPC_URL"),
28 // Bypasses alchemy gas estimation and instead uses Stackup for gas estimation
29 gasEstimator: async (userOp) => ({
30 ...userOp,
31 callGasLimit: "0x0",
32 preVerificationGas: "0x0",
33 verificationGasLimit: "0x0",
34 }),
35 // Uses alchemy fee estimation to comply with bundler
36 feeEstimator: alchemyFeeEstimator(alchemyTransport),
37 paymasterAndData: async (userop, opts) => {
38 const pmResponse: any = await stackupClient.request({
39 // @ts-ignore
40 method: "pm_sponsorUserOperation",
41 params: [
42 deepHexlify(await resolveProperties(userop)),
43 opts.account.getEntryPoint().address,
44 {
45 // @ts-ignore
46 type: "payg", // Replace with ERC20 context based on stackups documentation
47 },
48 ],
49 });
50 return {
51 ...userop,
52 ...pmResponse,
53 };
54 },
55});