Pay gas with any token
Gas fees paid in the native gas token can feel foreign to users that primarily hold stablecoins or your app’s own token. With our smart wallet, you can enable your users to pay for gas with ERC-20 tokens beyond the native gas token, like USDC or your own custom tokens, streamlining the user experience.
How it works: To enable this capability you will need to create a Gas Manager policy. When a user pays for gas with an ERC-20 token, we front the gas using the network’s native gas token and transfer the ERC-20 tokens from the user’s wallet to a wallet you control. The equivalent USD amount and the admin fee is then added to your monthly invoice.
Goal
Configure your React Native app to enable users to pay gas fees with an ERC-20 token (for example USDC).
Prerequisites
- Smart Wallets already installed and configured in your project.
- An Alchemy API key.
Steps
1 Create a Gas Manager policy
To enable your users to pay gas using an ERC-20 token, you need to create a “Pay gas with any token” Policy via the Gas Manager dashboard. You can customize the policy with the following:
- Receiving address: you must specify an address you own where the users’ ERC20 tokens will be sent to as they pay for gas. The token transfer to this address is orchestrated by the paymaster contract and happens automatically at transaction time.
- Tokens: you must select which tokens users should be able to pay gas with. Learn more here.
- ERC-20 transfer mode: choose when the user’s token payment occurs.
- [Recommended] After: No upfront allowance is required. The user signs an approval inside the same user operation batch, and the paymaster pulls the token after the operation has executed. If that post-execution transfer fails, the entire user operation is reverted and you still pay the gas fee.
- Before: You (the developer) must ensure the paymaster already has sufficient allowance—either through a prior
approve()
transaction or a permit signature—before the UserOperation is submitted. If the required allowance isn’t in place when the user operation is submitted, it will be rejected upfront.
- Sponsorship expiry period: this is the period for which the Gas Manager signature and ERC-20 exchange rate will remain valid once generated.

Now you should have a Gas policy created with a policy id you can use to enable gas payments with ERC-20 tokens.

2 Add the policy to your global config
import { const createConfig: (params: BaseCreateConfigProps) => AlchemyAccountsConfigCreates an AlchemyAccountsConfig by using the provided parameters to configure the core settings, including the required transport. It includes a signer creation function internally.
createConfig } from "@account-kit/react-native";
import { const sepolia: Chainsepolia } from "@account-kit/infra";
export const const config: AlchemyAccountsConfigconfig = function createConfig(params: BaseCreateConfigProps): AlchemyAccountsConfigCreates an AlchemyAccountsConfig by using the provided parameters to configure the core settings, including the required transport. It includes a signer creation function internally.
createConfig({
apiKey: stringapiKey: "ALCHEMY_API_KEY",
chain: Chainchain: const sepolia: Chainsepolia,
policyId?: string | string[] | undefinedpolicyId: "GAS_MANAGER_POLICY_ID", // your policy
policyToken: {
address: string;
maxTokenAmount: bigint;
}policyToken: {
address: stringaddress: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", // USDC_ADDRESS on sepolia (can be any ERC-20 token address enabled in your policy)
maxTokenAmount: bigintmaxTokenAmount: 10_000_000n, // Safety limit. If using USDC, this is 10 USDC (10 * 10^6).
},
});
All UserOperations
sent with the hooks will now be paid with the token configured above.
3 Send a sponsored UserOperation
import React from "react";
import { class ViewView, const Pressable: React.ForwardRefExoticComponent<PressableProps & React.RefAttributes<View>>Pressable, class TextText } from "react-native";
import { function useSendUserOperation<TEntryPointVersion extends GetEntryPointFromAccount<TAccount>, TAccount extends SupportedAccounts = SupportedAccounts>(params: UseSendUserOperationArgs<TEntryPointVersion, TAccount>): UseSendUserOperationResult<TEntryPointVersion, TAccount>A hook that returns functions for sending user operations. You can also optionally wait for a user operation to be mined and get the transaction hash before returning using waitForTx
. Like any method that takes a smart account client, throws an error if client undefined or is signer not authenticated.
useSendUserOperation } from "@account-kit/react-native";
export default function function PayWithTokenButton(): JSX.ElementPayWithTokenButton() {
const { const sendUserOperation: UseMutateFunction<SendUserOperationWithEOA<keyof EntryPointRegistryBase<unknown>>, Error, SendUserOperationParameters<SupportedAccounts>, unknown>sendUserOperation } = useSendUserOperation<keyof EntryPointRegistryBase<unknown>, SupportedAccounts>(params: UseSendUserOperationArgs<keyof EntryPointRegistryBase<unknown>, SupportedAccounts>): UseSendUserOperationResult<...>A hook that returns functions for sending user operations. You can also optionally wait for a user operation to be mined and get the transaction hash before returning using waitForTx
. Like any method that takes a smart account client, throws an error if client undefined or is signer not authenticated.
useSendUserOperation();
const const erc20Abi: anyerc20Abi = anyparseAbi([
"function approve(address spender, uint256 amount) public returns (bool)",
]);
return (
<class ViewView>
<const Pressable: React.ForwardRefExoticComponent<PressableProps & React.RefAttributes<View>>Pressable
PressableProps.onPress?: ((event: GestureResponderEvent) => void) | null | undefinedCalled when a single tap gesture is detected.
onPress={() =>
const sendUserOperation: (variables: SendUserOperationParameters<SupportedAccounts>, options?: MutateOptions<SendUserOperationWithEOA<keyof EntryPointRegistryBase<unknown>>, Error, SendUserOperationParameters<...>, unknown> | undefined) => voidsendUserOperation({
uo: UserOperationCallData | BatchUserOperationCallDatauo: [
{
// approve call
target: `0x${string}`target: anytokenAddress,
data: `0x${string}`data: anyencodeFunctionData({
abi: anyabi: const erc20Abi: anyerc20Abi,
functionName: stringfunctionName: "approve",
args: any[]args: [anypaymasterAddress, anymaxTokenAmount],
}) as `0x${string}`,
},
{
target: `0x${string}`target: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", // TARGET_ADDRESS
data: `0x${string}`data: "0x",
value?: bigint | undefinedvalue: 0n,
},
],
})
}
>
<class ViewView>
<class TextText>Send token-sponsored UO</class TextText>
</class ViewView>
</const Pressable: React.ForwardRefExoticComponent<PressableProps & React.RefAttributes<View>>Pressable>
</class ViewView>
);
}
4 (Optional) Override on a single client
Create a dedicated client if you only want certain operations to use the policy.
const { const client: anyclient } = anyuseSmartAccountClient({
policyId: stringpolicyId: "GAS_MANAGER_POLICY_ID",
policyToken: {
address: string;
maxTokenAmount: bigint;
}policyToken: {
address: stringaddress: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", // USDC_ADDRESS on sepolia (can be any ERC-20 token address enabled in your policy)
maxTokenAmount: bigintmaxTokenAmount: 10_000_000n, // Safety limit. If using USDC, this is 10 USDC (10 * 10^6).
},
});
Use this client
with any hook that sends UserOperations.
Done
Your app now sponsors gas with the specified ERC-20 token!