# Pay gas with any token

> Enable users to pay gas with tokens like USDC

> For the complete documentation index, see [llms.txt](/docs/llms.txt).

Gas fees paid in the native token can feel foreign to users that primarily hold stablecoins or your app's own token.
With Wallet APIs, 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 monthly invoice.

Post-operation mode is recommended for most use cases. If the token approval is batched with your calls and any call in the batch reverts, the approval is also reverted and you (the policy owner) pay the gas cost without receiving token compensation. See the [Token gas payment modes](#token-gas-payment-modes) section below for details on when to use pre-operation mode instead.

## Prerequisites

* API key from your [dashboard](https://dashboard.alchemy.com/apps)
* [A "pay gas with any token" policy](https://dashboard.alchemy.com/gas-manager/policy/create).

## Implementation

<Tabs>
  <Tab title="JavaScript" language="typescript">
    <Info>`@alchemy/wallet-apis` (v5.x.x) is currently in beta but is the recommended replacement for `@account-kit/wallet-client` (v4.x.x). If you run into any issues, please [reach out](mailto:support@alchemy.com).</Info>

Use the `paymaster` capability on the smart wallet client [`sendCalls`](/docs/wallets/reference/wallet-apis/functions/sendCalls) or [`prepareCalls`](/docs/wallets/reference/wallet-apis/functions/prepareCalls) actions. Set `autoApprove: true` in `postOpSettings` to automatically handle token approvals — the exact amount needed for gas is calculated and approved in the same operation.

<CodeBlocks>

```ts title="sendCalls.ts"
import { client, config } from "./client.ts";

const { id } = await client.sendCalls({
  capabilities: {
    paymaster: {
      policyId: config.policyId,
      erc20: {
        tokenAddress: config.gasToken,
        postOpSettings: {
          autoApprove: true,
        },
      },
    },
  },
  calls: [
    {
      to: "0x0000000000000000000000000000000000000000",
      value: BigInt(0),
      data: "0x",
    },
  ],
});
```

```ts title="client.ts"
import type { Hex } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { arbitrumSepolia } from "viem/chains";
import { createSmartWalletClient, alchemyWalletTransport } from "@alchemy/wallet-apis";

export const config = {
  policyId: "YOUR_POLICY_ID",
  gasToken: "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d", // USDC on Arbitrum Sepolia
} as const;

export const client = createSmartWalletClient({
  transport: alchemyWalletTransport({
    apiKey: "YOUR_API_KEY",
  }),
  chain: arbitrumSepolia,
  signer: privateKeyToAccount("0xYOUR_PRIVATE_KEY" as const),
});
```

</CodeBlocks>

<Accordion title="Using threshold mode instead">
  With threshold mode, you set a `below` threshold and an `amount`. An approval is only injected when the existing allowance drops below `below`, and it approves the fixed `amount` you configure. This avoids re-approving on every operation, which saves gas for frequent transactions.

  <CodeBlocks>

  ```ts title="sendCalls.ts"
  import { client, config } from "./client.ts";

  const { id } = await client.sendCalls({
    capabilities: {
      paymaster: {
        policyId: config.policyId,
        erc20: {
          tokenAddress: config.gasToken,
          postOpSettings: {
            autoApprove: {
              below: config.approveBelow,
              amount: config.approveAmount,
            },
          },
        },
      },
    },
    calls: [
      {
        to: "0x0000000000000000000000000000000000000000",
        value: BigInt(0),
        data: "0x",
      },
    ],
  });
  ```

  ```ts title="client.ts"
  import type { Address, Hex } from "viem";
  import { privateKeyToAccount } from "viem/accounts";
  import { arbitrumSepolia } from "viem/chains";
  import { createSmartWalletClient, alchemyWalletTransport } from "@alchemy/wallet-apis";

  export const config = {
    policyId: "YOUR_POLICY_ID",
    gasToken: "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d" as const, // USDC on Arbitrum Sepolia
    approveBelow: BigInt(1_000_000), // 1 USDC
    approveAmount: BigInt(10_000_000), // 10 USDC
  };

  export const client = createSmartWalletClient({
    transport: alchemyWalletTransport({
      apiKey: "YOUR_API_KEY",
    }),
    chain: arbitrumSepolia,
    signer: privateKeyToAccount("0xYOUR_PRIVATE_KEY" as const),
  });
  ```

  </CodeBlocks>
</Accordion>

  </Tab>

  <Tab title="API" language="bash">
    See the [`wallet_prepareCalls` API
reference](/docs/wallets/api/smart-wallets/wallet-api-endpoints/wallet-api-endpoints/wallet-prepare-calls)
for full descriptions of the parameters used in the following example.

Set `autoApprove: true` in `postOpSettings` to automatically handle the token approval. The exact amount needed for gas is calculated and approved in the same operation.

<Steps>
  <Step title="Prepare calls">
    Prepare calls using the `paymasterService` capability. Use your signer address directly as the `from` field to enable [EIP-7702](/docs/wallets/transactions/using-eip-7702) by default.

    ```bash
    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'",
                "postOpSettings": {
                  "autoApprove": true
                }
              }
            }
          },
          "calls": [
            {
              "to": "0x0000000000000000000000000000000000000000"
            }
          ],
          "from": "'$SIGNER_ADDRESS'",
          "chainId": "'$CHAIN_ID'"
        }
      ]
    }'
    ```

    <Accordion title="Using threshold mode instead">
      With threshold mode, an approval is only injected when the existing allowance drops below `below`, approving the fixed `amount` you configure. This avoids re-approving on every operation, which saves gas for frequent transactions.

      ```bash
      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'",
                  "postOpSettings": {
                    "autoApprove": {
                      "below": "'$APPROVE_BELOW'",
                      "amount": "'$APPROVE_AMOUNT'"
                    }
                  }
                }
              }
            },
            "calls": [
              {
                "to": "0x0000000000000000000000000000000000000000"
              }
            ],
            "from": "'$SIGNER_ADDRESS'",
            "chainId": "'$CHAIN_ID'"
          }
        ]
      }'
      ```
    </Accordion>
  </Step>
  <Step title="Sign and send">
    Sign the returned signature request and send using `wallet_sendPreparedCalls`. If the account hasn't been delegated yet, the response type will be `array` containing both an EIP-7702 authorization and a transaction. See the [send transactions guide](/docs/wallets/transactions/send-transactions) for a complete example.
  </Step>
</Steps>

  </Tab>
</Tabs>

<Accordion title="Using @account-kit/wallet-client (v4.x.x)?">
The examples on this page use `@alchemy/wallet-apis` (v5.x.x). If you're using `@account-kit/wallet-client` (v4.x.x), the client setup looks like this:

```ts title="client.ts (v4.x.x)"
import { LocalAccountSigner } from "@aa-sdk/core";
import { createSmartWalletClient } from "@account-kit/wallet-client";
import { alchemy, sepolia } from "@account-kit/infra";

const signer = LocalAccountSigner.privateKeyToAccountSigner("0xYOUR_PRIVATE_KEY" as const);

export const client = createSmartWalletClient({
  transport: alchemy({ apiKey: "YOUR_API_KEY" }),
  chain: sepolia,
  signer,
  account: signer.address, // can also be passed per action as `from` or `account`
  // Optional: sponsor gas for your users (see "Sponsor gas" guide)
  policyId: "YOUR_POLICY_ID", 
});
```

Key v4.x.x differences:
* **Account address** must be specified on the client or per action (`from` or `account`). In v5.x.x, the client automatically uses the owner's address as the account address via [EIP-7702](/docs/wallets/transactions/using-eip-7702).
* **Chain imports** come directly from `@account-kit/infra` instead of `viem/chains`.
* **Numeric values** use hex strings: `value: "0x0"` instead of `value: BigInt(0)`.
* In v4.x.x, the **paymaster capability** on `prepareCalls` or `sendCalls` is called `paymasterService` instead of `paymaster`, or you can set the `policyId` directly on the client.
* **Owners** use `LocalAccountSigner` / `WalletClientSigner` from `@aa-sdk/core`. In v5.x.x, a viem `LocalAccount` or `WalletClient` is used directly.

See the [full migration guide](/docs/wallets/resources/migration-v5) for a complete cheat sheet.
</Accordion>


## Advanced

<Accordion title="Usage with prepare calls">
  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.

  <Tabs>
    <Tab title="JavaScript" language="typescript">
      See the [`prepareCalls` SDK reference](/docs/wallets/reference/wallet-apis/functions/prepareCalls) for full parameter descriptions.
    </Tab>

    <Tab title="API" language="bash">
      See the [`wallet_prepareCalls` API
      reference](/docs/wallets/api/smart-wallets/wallet-api-endpoints/wallet-api-endpoints/wallet-prepare-calls)
    </Tab>
  </Tabs>
</Accordion>

<Accordion title="Token gas payment modes">
  The configured mode determines when the user’s token payment occurs.

  **\[Recommended] Post-Operation**

  * No upfront allowance is required.
  * If the paymaster does not already have a sufficient allowance, a token approval is injected into the same atomic calls batch as your operations. The paymaster contract pulls the token after execution.
  * When an approval is injected into the batch, if any call reverts, the approval is also reverted. The paymaster cannot collect the token payment and you (the policy owner) pay the gas cost without receiving token compensation.
  * If a sufficient allowance already exists (e.g., from threshold mode or a prior approval), no approval is injected and the paymaster can collect payment even if the batch reverts.

  **Pre-Operation:**

  * The paymaster contract 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](https://eips.ethereum.org/EIPS/eip-7597) signature.
  * Because the token transfer occurs during validation (before execution), the paymaster collects payment regardless of whether the operation reverts.
  * If the required allowance isn’t in place when the transaction is submitted, it will be rejected.

  **Choosing a mode:**

  Use **post-operation** mode when your operations are unlikely to revert. This mode:

  * Is the most gas efficient as it requires a single transfer.
  * Works with all ERC-20 tokens.
  * Requires a single signature from the user.

  Use **pre-operation** mode when your operations may revert. Because the token transfer happens before execution, the paymaster is always compensated regardless of the execution outcome. This is the safer choice for:

  * Swaps or other operations with dynamic outcomes
  * Any operation where revert conditions are difficult to predict ahead of time
  * High-value gas operations where uncompensated gas costs are unacceptable
</Accordion>

<Accordion title="Pre-operation mode implementation">
  <Steps>
  <Step title="Initial prepare calls">
    Prepare calls using the `paymasterService` capability. Use your signer address directly as the `from` field to enable [EIP-7702](/docs/wallets/transactions/using-eip-7702) by default.

    ```bash
    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": "'$SIGNER_ADDRESS'",
          "chainId": "'$CHAIN_ID'"
        }
      ]
    }'
    ```
  </Step>

  <Step title="(If needed) Sign permit request">
    If the response to step 1 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 4.
    The `data` field on the `paymaster-permit` response contains a [Permit typed message](https://eips.ethereum.org/EIPS/eip-2612). 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.
  </Step>

  <Step title="(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 2. 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:

    ```json
    {
      "capabilities": {
        "paymasterService": {
          "erc20": {
            "preOp": {
              "permitDetails": {
                "value": "0x...",
                "deadline": "0x...",
              }
            }
          }
        }
      }
      ... // same request as step 1
      "paymasterPermitSignature": "0x..."
    }
    ```
  </Step>

  <Step title="Sign and send">
    Sign and send the last prepared calls result as usual using `wallet_sendPreparedCalls`. If the account hasn't been delegated yet, the response type will be `array` containing both an EIP-7702 authorization and a transaction. See the [send transactions guide](/docs/wallets/transactions/send-transactions) for a complete example.
  </Step>
</Steps>

</Accordion>

<Accordion title="Estimating gas payments">
  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:

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

  Calling `prepareCalls` using the token gas payment 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.

  <Warning>
    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`.
  </Warning>
</Accordion>

<Accordion title="Auto-injected approvals">
  The Wallet APIs can automatically inject the required token approvals into the operation. There are two modes for post-operation approvals:

  **Exact mode (`autoApprove: true`)**

  The precise token amount needed is calculated after gas estimation and approved exactly — no more, no less. If the account already has sufficient allowance, no approval is added.

  This is ideal when you want minimal token exposure to the paymaster contract, don't want to estimate gas and figure out approval amounts yourself, or when users send infrequent transactions.

  **Threshold mode (`autoApprove: { below, amount }`)**

  An approval is only injected when the existing allowance drops below `below`, approving the fixed `amount` you configure. This avoids re-approving on every operation, which saves gas for frequent transactions.

  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.

  | | Exact mode (`true`) | Threshold mode (`{ below, amount }`) |
  |---|---|---|
  | Approval amount | Exact gas cost per operation | Fixed amount you configure |
  | Best for | Infrequent transactions, minimal token exposure | Frequent transactions, lower gas overhead |
  | Re-approves | Only when existing allowance is insufficient | Only when allowance drops below `below` |
</Accordion>

<Accordion title="How exact mode works under the hood">
  When `autoApprove: true` is set, the following steps are performed during request preparation:

  1. **Injects a sentinel approval** — A temporary `approve(paymaster, maxUint256)` call is prepended to your calls so gas estimation accounts for the approval's execution cost.
  2. **Estimates gas** — The full transaction (including the sentinel) is sent through gas estimation.
  3. **Quotes the token cost** — The paymaster contract returns an exchange rate, and the server calculates the exact token amount: `(maxNativeGasCost × exchangeRate) / scaler`.
  4. **Checks current allowance** — If the account's existing allowance already covers the quoted amount, the approval is stripped entirely. Otherwise, it's updated to approve only the exact amount.
  5. **Returns the final transaction** — The `callData` is re-encoded with the finalized calls.

  When an approval is needed, it is prepended to the calls array and executed atomically in the same transaction. The approved amount is the exact token cost quoted by the paymaster contract, not a fixed or maximum value.
</Accordion>

<Accordion title="Auto-injected permits (pre-operation mode)">
  <Note>
    This is an advanced feature. Use post-operation mode for most use cases.
  </Note>

  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](https://eips.ethereum.org/EIPS/eip-7597) compliant.
  2. Expose a `version()` function.
  3. Utilize the canonical `DOMAIN_SEPARATOR` outlined in [ERC-2612](https://eips.ethereum.org/EIPS/eip-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.
</Accordion>

## Next steps

Build more:

* [Sponsor gas for users](/docs/wallets/transactions/sponsor-gas)

Troubleshooting:

* [Gas manager errors](/docs/reference/gas-manager-errors)