Pay gas with any token

Gas fees paid in the native token can feel foreign to users that primarily hold stablecoins or your app’s own token. With Smart Wallets, you can allow users to pay for gas with any token, streamlining the user experience.

How it works

When a user pays for gas with a token, the gas is fronted using the network’s native gas token and the payment tokens are transferred from the user’s wallet to a wallet you configure in the policy. The equivalent USD amount and the admin fee are then added to your’s monthly invoice.

Post-operation mode is recommended. This mode requires users to hold enough of the gas token in their wallet after their operation completes to pay the gas fee. If the balance is insufficient, the transaction reverts and you sponsor any gas used without token payment. If this doesn’t work for your use case, see the Token gas payment modes section below for more options.

Prerequisites

Implementation

Required SDK version: ^v4.61.0

Use the useSendCalls hook and the paymasterService capability parameter to send calls with an ERC20 token paymaster.

Prerequisites

See the useSendCalls SDK reference for parameter descriptions.

1import { useSendCalls, useSmartAccountClient } from "@account-kit/react";
2import { config } from "./config";
3
4export default function SendCalls() {
5 const { client } = useSmartAccountClient({});
6 const { sendCallsAsync } = useSendCalls({ client });
7
8 const handleSend = async () => {
9 if (!client) {
10 throw new Error("Smart account client not connected");
11 }
12
13 try {
14 const { ids } = await sendCallsAsync({
15 capabilities: {
16 paymasterService: {
17 policyId: config.policyId,
18 erc20: {
19 tokenAddress: config.gasToken,
20 postOpSettings: {
21 autoApprove: {
22 below: config.approveBelow,
23 amount: config.approveAmount,
24 },
25 },
26 },
27 },
28 },
29 calls: [
30 {
31 to: "0x0000000000000000000000000000000000000000",
32 value: "0x00",
33 data: "0x",
34 },
35 ],
36 });
37
38 console.log("Transaction sent with ID:", ids[0]);
39 } catch (error) {
40 console.error(error);
41 }
42 };
43
44 return <button onClick={handleSend}>Click to Send</button>;
45}

Advanced

Pay gas with any token also works with the prepare calls methods in the various frameworks. Usage of the capability will be the same as when using send calls. It is recommended to use prepare calls if you want to inspect the prepared call prior to prompting the user for signature.

The configured mode determines when the user’s token payment occurs.

[Recommended] Post-Operation

  • No upfront allowance is required.
  • The user signs an approval inside the same calls batch, and the paymaster pulls the token after the operation has executed.
  • If that post-operation transfer fails, the entire batch is reverted and you (the developer) pay the gas fee.

Pre-Operation:

  • The paymaster must have an allowance prior to the operation.
  • This can be done either through a prior call to approve() or by using an ERC-7597 Permit signature.
  • If the required allowance isn’t in place when the user operation is submitted, it will be rejected.

Post-operation mode is recommended for most use cases. This mode:

  • Is the most gas efficient as it only requires a single transfer.
  • Works with all ERC-20 tokens.
  • Only ever requires a single signature from the user.

However, because tokens are deducted after execution, you may be required to pay for gas without receiving sufficient token payment. You should ensure that users have enough token left over to pay for gas after the operation, otherwise they won’t receive payment from users for gas and the operation will revert. If the operation results in a static amount of the user’s token balance after execution and you can account for this before submitting the operation, use PostOp mode.

Examples of static amounts:

  • Payments, purchases, and deposits
  • Operations unrelated to the payment token

Examples of dynamic amounts:

  • Swaps that include the payment token

If you sponsor operations that result in dynamic amounts of the payment token left over, consider using pre-operation mode. See an example implementation below.

1

(If Needed) Request Account

If the user does not yet have a smart account, you must create one.

$ ACCOUNT_ADDRESS=$(curl --request POST \
> --url https://api.g.alchemy.com/v2/$ALCHEMY_API_KEY \
> --header 'accept: application/json' \
> --data '
>{
> "id": 1,
> "jsonrpc": "2.0",
> "method": "wallet_requestAccount",
> "params": [
> {
> "signerAddress": "'$SIGNER_ADDRESS'"
> }
> ]
>}
>' | jq -r '.result.accountAddress')
2

Initial Prepare Calls

Prepare calls using the paymasterService capability.

$curl --request POST \
> --url https://api.g.alchemy.com/v2/$ALCHEMY_API_KEY \
> --header 'accept: application/json' \
> --data '
>{
> "id": 1,
> "jsonrpc": "2.0",
> "method": "wallet_prepareCalls",
> "params": [
> {
> "capabilities": {
> "paymasterService": {
> "policyId": "'$ALCHEMY_POLICY_ID'",
> "erc20": {
> "tokenAddress": "'$GAS_TOKEN'",
> "preOpSettings": {
> "autoPermit": {
> "below": "'$APPROVE_BELOW'",
> "amount": "'$APPROVE_AMOUNT'"
> }
> }
> }
> }
> },
> "calls": [
> {
> "to": "0x0000000000000000000000000000000000000000"
> }
> ],
> "from": "'$ACCOUNT_ADDRESS'",
> "chainId": "'$CHAIN_ID'"
> }
> ]
>}'
3

(If Needed) Sign Permit Request

If the response to step 2 is a type paymaster-permit, then the user must sign the signature request and return via a second call to wallet_prepareCalls. Else, skip to step 5. The data field on the paymaster-permit response contains a Permit typed message. It is recommended that the user checks the fields of this message prior to calculating its hash. The signatureRequest field on the paymaster-permit response is a required signature over the calculated hash. This is typically an ERC-712 typed signature envelope with a field for the hash to sign over.

4

(If Needed) Second Prepare Calls

After signing, another call to wallet_prepareCalls is needed to encode the permit into the operation. As a convenience, the paymaster-permit response type returns a modifiedRequest parameter that modifies the initial request with the permit details. The second request is the modifiedRequest with an additional paymasterPermitSignature field set to the signature from step 3. The user can also choose to recreate the request and set the corresponding fields in the paymasterService capability to the details returned in the permit signature. For example:

{
"capabilities": {
"paymasterService": {
"erc20": {
"preOp": {
"permitDetails": {
"value": "0x...",
"deadline": "0x...",
}
}
}
}
}
... // same request as step 2
"paymasterPermitSignature": "0x..."
}
5

Sign & Send

Sign and send the last prepared calls result as usual using wallet_sendPreparedCalls. See the send transactions guide for more info.

To show users the gas cost in their chosen gas token prior to signing and sending their request, use the prepareCalls hook/action/api over the sendCalls version as sendCalls doesn’t surface the fee payment information during sign and send.

The return type of prepareCalls contains a feePayment field containing fee information. Display this information to users prior to signing the operation. For example:

"feePayment": {
"sponsored": false,
"tokenAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"maxAmount": "0x1d33"
}

Calling prepareCalls using the token paymaster capability counts against your policy’s pending total. If you intend on making many calls to surface the best price, set the onlyEstimation parameter for estimation, and then remove only when the user intends to sign the result.

Requests to prepareCalls count against your policy’s pending total. Use onlyEstimation if the intention is to query for the fee and not to sign the operation. Note that onlyEstimation does not work with the sendCalls requests and must be used with prepareCalls.

The Wallet APIs can be configured to automatically inject the required approvals into the operation when needed. The settings for this depend on the mode being used.

Post-operation mode

In post-operation mode, you can set the paymaster service capability to automatically inject approvals to the paymaster contract using the erc20.postOp.autoApprove field. The approval will be batched with the provided calls to ensure it’s in place before the transfer to the paymaster during post-operation. To save gas, the approval is only injected if the sender’s existing allowance on the paymaster contract is less than the below parameter. The injected approval will be set to the amount parameter.

Set below high enough to reasonably cover the gas cost for your most expensive operations. Set amount to a value you feel comfortable maintaining as an allowance on the paymaster contract. Setting a large amount is not recommended for security purposes.

Pre-operation mode

This is an advanced feature. Use post-operation mode for most use cases.

In pre-operation mode, you can set the paymaster service capability to automatically return a request for a Permit signature using the erc20.preOp.autoPermit field. This permit will allow an allowance to be set on the paymaster contract from the sender prior to any token transfers needed during pre operation.

To be eligible for auto-injected permits, the payment gas token must:

  1. Be ERC-7597 compliant.
  2. Expose a version() function.
  3. Utilize the canonical DOMAIN_SEPARATOR outlined in ERC-2612.

If the user already has an allowance on the paymaster contract, the normal flow is used. If the user needs an approval, the following steps inject the permit:

  1. The prepare request returns a signature request for the paymaster permit.
  2. The user signs the permit request and the signature request back in a second prepare request.
  3. The permit signature is injected into the paymaster contract calldata, and a normal operation is returned.
  4. The user proceeds as normal, signing and sending the operation.

Next steps

Build more:

Troubleshooting: