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.
2. Create a Smart Account Client
Now you can create a Smart Account Client which is configured to sponsor gas.
import {
alchemy,
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 { generatePrivateKey } from "viem/accounts";
const alchemyTransport = alchemy({
apiKey: "YOUR_API_KEY",
});
export const client = createAlchemySmartAccountClient({
transport: alchemyTransport,
policyId: "YOUR_POLICY_ID",
chain: sepolia,
account: await createLightAccount({
chain: sepolia,
transport: alchemyTransport,
signer: LocalAccountSigner.privateKeyToAccountSigner(generatePrivateKey()),
}),
});
3. Send a user operation with gas sponsorship
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 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,
alchemy,
} 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 alchemyTransport = alchemy({
apiKey: "API_KEY",
});
const alchemyRpcClient = createAlchemyPublicRpcClient({
chain: sepolia,
transport: alchemyTransport,
});
// 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: alchemyTransport,
});
// 3. create smart account client
export const client = createSmartAccountClient({
transport,
chain: sepolia,
account: await createLightAccount({
chain: sepolia,
transport: alchemyTransport,
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 alchemyTransport = alchemy({
apiKey: "API_KEY",
});
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: alchemyTransport,
});
// 3. create smart account client
export const client = createSmartAccountClient({
transport,
chain: sepolia,
account: await createLightAccount({
chain: sepolia,
transport: alchemyTransport,
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,
};
},
});