Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

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
npm install permissionless viem

Steps

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.

Related