How to pay gas fees for an AI agent's wallet
This guide shows you how to pay gas for an AI agent that sends transactions from its own wallet, without funding the agent with ETH. The agent gets a smart account, and Pimlico's verifying paymaster sponsors its gas from your Pimlico balance. If you'd rather the agent pay its own gas, see Let the agent pay its own gas in USDC.
This guide uses Base. The same code works on any supported chain once you change the chain, the RPC URL and the USDC address.
Prerequisites
- A Pimlico API key on a plan that includes mainnets. Create one.
- A signer for the agent from a key management service or server wallet (Turnkey, Privy, Fireblocks, a cloud KMS) that you can load as a viem
LocalAccount. See signers.
npm install permissionless viemSteps
Create the agent's smart account
On your backend, create a Safe smart account owned by the agent's signer and a smart account client that uses Pimlico as bundler and paymaster.
import { createSmartAccountClient } from "permissionless"
import { toSafeSmartAccount } from "permissionless/accounts"
import { createPimlicoClient } from "permissionless/clients/pimlico"
import { prepareUserOperationForErc20Paymaster } from "permissionless/experimental/pimlico"
import { createPublicClient, http, type LocalAccount } from "viem"
import { entryPoint07Address } from "viem/account-abstraction"
import { base } from "viem/chains"
const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" // USDC on Base
const pimlicoUrl = `https://api.pimlico.io/v2/${base.id}/rpc?apikey=YOUR_PIMLICO_API_KEY`
const publicClient = createPublicClient({
chain: base,
transport: http("https://mainnet.base.org"),
})
const pimlicoClient = createPimlicoClient({
chain: base,
transport: http(pimlicoUrl),
entryPoint: {
address: entryPoint07Address,
version: "0.7",
},
})
// The agent's signer: a Turnkey, Privy server wallet, Fireblocks or KMS-backed account.
declare const agentSigner: LocalAccount
const agentAccount = await toSafeSmartAccount({
client: publicClient,
owners: [agentSigner],
entryPoint: {
address: entryPoint07Address,
version: "0.7",
},
version: "1.4.1",
})
const agentClient = createSmartAccountClient({
account: agentAccount,
chain: base,
bundlerTransport: http(pimlicoUrl),
paymaster: pimlicoClient,
userOperation: {
estimateFeesPerGas: async () => {
return (await pimlicoClient.getUserOperationGasPrice()).fast
},
// Only changes behaviour when paymasterContext contains a `token`
prepareUserOperation: prepareUserOperationForErc20Paymaster(pimlicoClient),
},
})Give the agent a sponsored transaction tool
Wrap sendUserOperation in a function that waits for the receipt and returns only the outcome, and register it as a tool in your agent framework. With no paymasterContext, Pimlico sponsors the gas.
import type { Address, Hex } from "viem"
type AgentCall = { to: Address; value: bigint; data: Hex }
// Register this as a tool in your agent framework. The model only sees the result.
export async function sendAgentTransaction(calls: AgentCall[]) {
// No paymasterContext: Pimlico sponsors the gas from your balance
const hash = await agentClient.sendUserOperation({ calls })
const receipt = await agentClient.waitForUserOperationReceipt({ hash })
return {
success: receipt.success,
transactionHash: receipt.receipt.transactionHash,
error: receipt.success ? undefined : (receipt.reason ?? "reverted"),
}
}The agent can now call the tool with any list of calls. Check success in the result: a user operation can be included onchain and still revert.
Let the agent pay its own gas in USDC
To have the agent pay instead of you, fund its account address with USDC and pass the token in the paymasterContext. The agent never needs ETH.
// The agent pays its own gas from its USDC balance
const hash = await agentClient.sendUserOperation({
calls: [{ to: "0x1111111111111111111111111111111111111111", value: 0n, data: "0x" }],
paymasterContext: { token: USDC },
})To show the fee before sending, see How to calculate UserOperation's costs using the ERC-20 Paymaster.
Run many agents
Create one smart account per agent, each from its own signer. If one agent sends several transactions at once, give each its own nonce key, as described in How to send multiple user operations in parallel.
Use an agent's existing EOA
If the agent already has an EOA with funds in it, keep its address with EIP-7702: replace toSafeSmartAccount with to7702SimpleSmartAccount (EntryPoint 0.8) and attach a signed authorization to the first user operation. See the EIP-7702 quickstart.
Let a key you control recover the agent's funds
To recover funds or rotate the agent's key, add a key you control as a second owner of the Safe. See How to create and use a Safe account with multiple signers.
Limit what you sponsor (optional)
To cap how much gas an agent can spend, create a policy in the sponsorship policies dashboard with a per-user limit (one "user" is one agent account) and pass its ID in the paymasterContext. When an agent hits the limit, this example falls back to paying gas from the agent's USDC:
const AGENT_POLICY_ID = "sp_your_agent_policy"
// In sendAgentTransaction, call this instead of agentClient.sendUserOperation
async function sendWithinLimits(calls: AgentCall[]) {
try {
// Sponsored only within the policy's limits
return await agentClient.sendUserOperation({
calls,
paymasterContext: { sponsorshipPolicyId: AGENT_POLICY_ID },
})
} catch {
// Limit reached: the agent pays gas from its own USDC
return agentClient.sendUserOperation({
calls,
paymasterContext: { token: USDC },
})
}
}To sponsor based on which contracts the agent calls, add a sponsorship policy webhook. For all policy options, see How to use sponsorship policies.