Your agent has USDC on Base. It wants to approve a token on Arbitrum, or call a contract on Polygon. Its balance of native gas on that chain is zero, so every transaction fails with insufficient funds for gas.
x402 doesn't hit this problem on the payment side: the buyer signs an EIP-3009 transferWithAuthorization and the facilitator broadcasts it, so paying needs no ETH. The problem shows up as soon as the agent has to send its own transaction.
This post shows a small, working way out: one x402 call that turns USDC on Base into native gas on Base, Arbitrum, Optimism or Polygon. It uses a service I run (AgentGas, disclosure below). The pattern carries over to any x402 seller.
What the call looks like
GET https://agentgas.madebydevx.com/v1/gas?chain=arbitrum&amount=0.0002&to=0xYOUR_AGENT
- With no payment header you get HTTP 402 and a
PAYMENT-REQUIREDheader: x402 v2, schemeexact, networkeip155:8453, asset USDC, plus the exact amount. - An x402 client signs that and retries. The server verifies and settles through a facilitator, then answers 202 with an
order_id. - You poll
/v1/order?id=...until it readsdelivered(it includespayout_txon the destination chain) orrefunded(it includes a USDCrefund_txon Base).
to is optional. If you leave it out, the gas goes to the address that signed the payment. Pass it explicitly for smart-contract wallets, since their address can differ across chains.
Before paying anything, you can check price and capacity for free:
curl -s "https://agentgas.madebydevx.com/v1/quote?chain=arbitrum&amount=0.0002"
curl -s "https://agentgas.madebydevx.com/v1/status" # live float per chain, daily cap
When I wrote this (Oct 21, 2026), the quote for 0.0002 ETH on Arbitrum came back at 0.612 USDC, and 0.5 POL on Polygon at 0.078 USDC. Pricing is the Chainlink spot value of the gas, plus 20%, plus $0.02.
The full script (Node 18+)
npm i @x402/fetch @x402/evm viem
AGENT_KEY=0x... node get-gas.mjs polygon 0.5
// get-gas.mjs: an agent wallet holding USDC on Base (and 0 ETH) buys native gas.
import { wrapFetchWithPayment, x402Client } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm";
import { privateKeyToAccount } from "viem/accounts";
const account = privateKeyToAccount(process.env.AGENT_KEY);
const [chain = "base", amount = "0.0001", to = account.address] = process.argv.slice(2);
const client = x402Client.fromConfig({
schemes: [{ network: "eip155:8453", client: new ExactEvmScheme(account) }],
spendControls: { maxAmountPerPayment: "$1" }, // hard cap per call
});
const pay = wrapFetchWithPayment(fetch, client);
const base = "https://agentgas.madebydevx.com";
const q = await (await fetch(`${base}/v1/quote?to=${to}&chain=${chain}&amount=${amount}`)).json();
console.log(`quote: ${amount} ${q.native} on ${chain} for ${q.price_usdc} USDC`);
const res = await pay(`${base}/v1/gas?to=${to}&chain=${chain}&amount=${amount}`);
const order = await res.json();
console.log(res.status, order);
if (res.status !== 202 && res.status !== 200) process.exit(1);
for (let i = 0; i < 30; i++) { // poll until delivered or refunded
await new Promise(r => setTimeout(r, 5000));
const o = await (await fetch(`${base}/v1/order?id=${order.order_id}`)).json();
console.log(o.status, o.payout_tx || o.refund_tx || "");
if (["delivered", "refunded"].includes(o.status)) break;
}
Two things worth noticing:
-
spendControls.maxAmountPerPaymentmatters more than it looks. An agent that pays whatever a 402 asks for is an agent that can be drained. Cap it. - The agent's wallet needs USDC on Base and no ETH. The payment is just a signature.
What I actually ran
I ran the script just now with a fresh, empty key. The client fetched the quote, signed the challenge and retried, and the server forwarded the payment to the facilitator. That came back as invalid_exact_evm_insufficient_balance, which is what should happen for a wallet holding 0 USDC.
The paid path was tested end to end on Base mainnet earlier with my own wallet as the buyer:
- Delivered: 0.138 USDC paid over x402 (payment tx), and 0.00004 ETH arrived about 17 seconds later (payout tx).
- Cross-chain: paid on Base (tx), delivered on Optimism (tx).
- Refund path: the recipient was a contract that rejects ETH, and the USDC went back automatically (refund tx).
Those were self-tests, not customers.
If your agent speaks MCP
There is also a read-only MCP server at https://agentgas.madebydevx.com/mcp (streamable HTTP, listed in the official MCP Registry as com.madebydevx.agentgas/agentgas). It has four tools: agentgas_quote, agentgas_status, agentgas_order and agentgas_how_to_buy. The MCP server can't sign payments for you. agentgas_how_to_buy hands back the exact x402 URL, and your x402 client pays it.
Design choices, if you build your own
- Reject before settling. A bad address, an unsupported chain or a lack of float should fail at the 402 stage, so nobody pays for a delivery that can't happen.
- Refund in the currency you were paid in, on the chain you were paid on. That keeps the buyer's accounting simple.
-
Publish capacity. A small float runs out.
/v1/statuslets an agent check before it commits. -
Make it discoverable to machines. The service has
/openapi.json,/.well-known/x402(x402 v2 withaccepts[]) and/llms.txt, so an agent can find it and call it without reading a web page.
Limits and disclosure
- AgentGas is a small beta I run as DevX Tools. Max $2 per order and $10 per day across the service. The float is small, so check
/v1/statusfirst. - It is independent and not affiliated with Coinbase, Base, Arbitrum, Optimism, Polygon or the x402 Foundation.
- If you only need gas for a user operation on a smart account, a paymaster may suit you better. This is for plain EOAs and agents that just need a little native balance on another chain.
Docs for agents: https://agentgas.madebydevx.com/llms.txt. Questions or bug reports: devx@mail.grokbot.com.
Top comments (0)