Gasless nanopayments on Arc, and the nonce that stays at 0
Deep DiveAI AgentsAgent Paymentsx402

Gasless nanopayments on Arc, and the nonce that stays at 0

An AI agent paid for eleven queries without ever sending a transaction. How EIP-3009 authorizations and Circle Gateway batching make that work, and who pays for what.

Ignacio Pastor Sanchez
October 6, 2026
17 min read

TL;DR

I built Turnstile, a paid lane for onchain data agents. A buyer agent asks a seller for an answer about a Uniswap pool, gets an HTTP 402, pays, and gets the answer. One of the payment rails runs on Arc testnet, settled through Circle Gateway nanopayments.

The property I wanted is simple to say: the agent that spends money should never pay gas. Someone still pays, so this post follows each cost to whoever covers it, and ends with the one number on chain that shows it.

  • The agent signs an EIP-3009 TransferWithAuthorization offchain. That is a signature, not a transaction. No transaction, no gas.
  • Circle Gateway credits the seller immediately, then batches many authorizations into one onchain transaction, submitted and paid for by Circle's batcher. The batching is what makes a $0.0005 payment make economic sense.
  • The agent's spending balance is funded by a different wallet (the "warm tier") calling depositFor(token, agent, value). The caller pays that gas; the agent receives the balance.
  • Circle's batcher is the gas sponsor, though not an ERC-4337 paymaster: there is no smart account and no EntryPoint. On Arc, USDC is also the native gas token, so the agent's wallet holds 0 USDC on chain. Its spending money sits in Circle's GatewayWallet contract.
  • The seller is credited inside Gateway and pays gas only when it withdraws to its own wallet.
  • After eleven settled payments, the agent's eth_getTransactionCount is still 0. It has never sent a transaction, so it has never paid a wei of gas.

The code is on Github.

Part 2 of 9 in the Turnstile series: building a paid lane for onchain data agents.

An agent with a wallet

Turnstile is a market where AI agents buy answers or data from other agents. The seller in this series answers one question about a Uniswap pool, "is it safe to LP?", for $0.07 a query, or $0.35 for a premium answer that includes the evidence behind the verdict. The buyer is an agent too: it finds the seller, gets a 402 Payment Required with the price, pays, and gets the answer, with no human in the loop.

That makes the buyer an agent with a wallet, and two things about it are uncomfortable.

  1. Agents that pay per call push prices toward fractions of a cent, and a sub-cent payment could be smaller than the gas it would take to send it.
  2. The agent's wallet private key sits next to an LLM loop, a program that decides what to do from text it reads. You want to be able to control that key so it can only pay for queries and nothing else.

This post shows how Turnstile's Arc rail solves both. The agent never sends a transaction. It signs it without broadcasting it, and Circle Gateway collects the signatures and settles them in batches. Along the way you'll see where each bit of gas goes and who pays for it, and why one number on chain, the agent's nonce, shows the agent itself never paid any.

If you're building agents that pay for things, Circle ships this on mainnet today, so you can use the same design.

Fractions of a cent

On Arc testnet, the deposit that funded the whole mandate cost 0.0019 USDC in gas. That is roughly four times the price of one $0.0005 query.

Turnstile's seller sells a verdict on a liquidity pool, at $0.07 for the standard tier. The demo also runs the same query at $0.0005, because sub-cent pricing is where agent-to-agent commerce gets interesting: an agent that calls a data service a few hundred times an hour does not want a card network, and it does not want to pay a transaction fee per call. Two constraints fall out of that.

  1. A payment smaller than its transaction fee isn't worth settling on its own. Sending a $0.0005 payment when its own transaction costs about four times the payment in gas, would make someone lose money on every query.
  2. The key that spends should have as few permissions as possible. Turnstile has a cold/warm/hot key hierarchy, and its invariant is the key that spends can never raise its own limit. If the hot key can send arbitrary transactions, it ould do much more than pay for queries.

The first constraint says "batch". The second says the agent should hold no gas and send no transactions, so the only money it can ever move is what the org deposited for it, under a cap the agent can't change. One mechanism gives you both.

Where that money can go is the mandate's job. Before signing anything, the agent's payment code checks the 402 against the mandate, and refuses to pay to any seller the mandate doesn't name or any price above its per-query ceiling. Part 7 covers how the org issues that mandate.

EIP-3009: moving tokens with a signature

EIP-3009 (still in Draft status by the way, though USDC has shipped it for years) adds functions to an ERC-20 that let a token holder authorize a transfer with an EIP-712 signature, and let someone else execute it. Its motivation section is clear about why: it lets you "delegate the gas payment to someone else".

The core function, from the spec:

// From the EIP-3009 specification (not Turnstile code)
function transferWithAuthorization(
    address from,
    address to,
    uint256 value,
    uint256 validAfter,
    uint256 validBefore,
    bytes32 nonce,
    uint8 v,
    bytes32 r,
    bytes32 s
) external;

The payer signs (from, to, value, validAfter, validBefore, nonce). Whoever holds that signature can call the function and pay the gas. Two details matter for agents.

  • The nonce is a random bytes32, not a counter. The spec argues sequential nonces make concurrent meta-transactions fragile, so an agent can sign many authorizations at once without them tripping over each other. It also means the EIP-3009 nonce has nothing to do with the account nonce at the end of this post. Different nonce, different layer.
  • The signature commits to an exact value. You cannot redeem it for an amount different from the one the sender intends.

This is what x402's exact scheme does on EVM. The spec says the scheme "executes a transfer where the Facilitator (server) pays the gas, but the Client (user) controls the exact flow of funds via cryptographic signatures", and lists EIP-3009 as the recommended transfer method, "truly gasless".

Settlement is the facilitator calling transferWithAuthorization with the payload's signature. (Turnstile's other rail, on Hedera, has to do without EIP-3009 entirely; that story is in x402 on Hedera.)

So plain x402 on EVM already gives you a gasless payer. What it does not give you is batching: one authorization, one transferWithAuthorization call, one transaction. A facilitator paying $0.0019 of gas to collect $0.0005 is still nonsense so we need a way to make this make economic sense.

Circle Gateway: same signature, different verifying contract

Circle Gateway nanopayments keep the x402 exact shape and change two things.

  • The authorization spends from a balance the payer deposited into Circle's GatewayWallet contract, rather than from the payer's wallet.
  • Settlement is batched: Gateway credits the seller at once and settles many payments in one transaction later.

Circle's docs call it "gas-free USDC nanopayments down to $0.000001, powered by Circle Gateway batched settlement", where "buyers sign payment authorizations offchain at zero gas cost".

Here is the flow as it ran in Turnstile.

  1. The payer's balance lives in a contract. USDC is deposited into Circle's GatewayWallet contract once. That is an onchain transaction, and whoever deposits pays its gas. In Turnstile that is the buyer organization, not the agent.
  2. The authorization is signed against the GatewayWallet, not USDC. The EIP-712 verifyingContract in the 402 challenge is the GatewayWallet (0x0077777d…19b9 on Arc testnet), and the domain name is GatewayWalletBatched. So a signature is a claim on a Gateway balance, not on a token allowance.
  3. The seller posts it to Gateway's /v1/x402/settle. Gateway credits the seller immediately and returns an authorization id, which is a UUID, not a transaction hash.
  4. Circle's batcher later redeems many authorizations in one transaction against the GatewayWallet. The transaction's from is Circle's batcher address, and the batcher pays its gas. No payer appears as a sender.

Step 2 deserves a second look. The seller's rail doesn't hardcode the verifying contract. It reads the EIP-712 domain from Gateway's own /supported response on every quote, so if Circle redeploys the GatewayWallet, the new address reaches buyers with no code change. If /supported stops giving a usable domain, the seller answers 503 instead of quoting a payment that nobody could sign.

return {
  scheme: 'exact',
  network,
  asset,
  amount: usdToAtomic(req.priceUsd, decimals, 1),
  payTo,
  maxTimeoutSeconds: req.maxTimeoutSeconds ?? maxTimeoutSeconds,
  extra: {
    // `name`, `version` and `verifyingContract` are the EIP-712 domain and
    // are not optional: a client cannot sign without them. Spread from the
    // facilitator's own answer rather than restated, so a redeploy on
    // Circle's side propagates without a code change here.
    ...kindExtra,
    facilitator: gateway.baseUrl,
    settlementModel: 'gateway-batched-authorization',
    // ...
    settlementTiming: 'batched-asynchronous',
  },
};

Trimmed from rails/arc-usdc/index.ts, the Arc rail's challenge().

On the buyer side there is almost nothing to write, which is the point. The agent's signer hands its key to Circle's own client scheme from @circle-fin/x402-batching, which builds and signs the EIP-3009 message for exactly the quoted amount:

import { BatchEvmScheme } from '@circle-fin/x402-batching/client';
import { privateKeyToAccount } from 'viem/accounts';

// ...
const account = privateKeyToAccount((rawKey.startsWith('0x') ? rawKey : `0x${rawKey}`) as Hex);
const scheme = new BatchEvmScheme(account);

From buyer/watchdog/arc-signer.ts.

There is no wallet client and no RPC transport, nothing on the agent's side that could send a transaction. The message itself is built and signed by Circle's BatchEvmScheme. Circle runs the contract that verifies these signatures, so using its own encoder keeps the EIP-712 domain and fields exactly as the GatewayWallet expects.

What batching looked like on the chain

Sampling 100 consecutive Gateway transfers on Arc testnet, they resolved to 9 distinct transaction hashes, 12 to 13 authorizations each.

Turnstile's first run sent seven payments (one $0.07 query and six $0.0005 nanopayments), and all of them landed in a single transaction, 0xd6e77a59…a0c1, in block 60940635. That transaction carried 22 payments in total, ours plus fifteen other Gateway users', moving 0.133111 USDC. A second run's four payments landed in 0xe50b8be6…5c39, block 60942503. So eleven payments went through only two transactions, both sent by Circle's batcher.

Circle's batcher sends the batch transaction on its own schedule, so a Gateway payment has two moments. It is settled when /settle succeeds: Gateway credits the seller and debits the payer in its own balances, and Turnstile's seller then delivers the answer.

The onchain transaction comes later, whenever the batcher sends it. That's why /settle returns an authorization id, not a transaction hash.

Funding the agent without the agent: depositFor

If the agent never transacts, how does it get a Gateway balance? Depositing into the GatewayWallet is a transaction, and the agent doesn't send transactions.

The GatewayWallet has depositFor(address token, address depositor, uint256 value). Circle's contract reference describes it as depositing "on behalf of another address after approving this contract for the token. The resulting balance belongs to the address specified by the depositor parameter, not the function caller."

That one function is the whole key hierarchy. The warm tier (the buyer organization's wallet) approves USDC, calls depositFor naming the agent's address, and pays the gas. The hot tier receives spending power but signs nothing onchain.

result.depositTxHash = await signAndSend({
  ...base,
  to: gateway,
  data: encodeFunctionData({ abi: DEPOSIT_FOR_ABI, functionName: 'depositFor', args: [usdc, agentAddress, amount] }),
  value: 0,
  nonce,
  gas_limit: hex(DEPOSIT_GAS),
});

Trimmed from buyer/org/fund-agent.ts. nonce here is the org wallet's nonce, which is expected to move.

Circle's nanopayments SDK reference documents a deposit() method on the buyer's client, not depositFor, so I call the contract function directly rather than through the SDK.

The warm tier is a Privy server wallet owned by an operations key quorum, with a policy (owned by a separate 2-of-2 board quorum) that pins depositFor.depositor to the agent and caps depositFor.value. Privy signs the transaction inside its enclave, and Turnstile broadcasts it to Arc's public RPC, because Privy does not route RPCs for Arc testnet. The details are in Spending mandates for agents.

A deposit transcript shows both sides of the hierarchy in one screen:

warm / org    0x3De96375140717193f52c220Df5Ec460971cbE84
  nonce       0 -> 2      (it transacts; this is expected to move)
  on chain    2.000000 -> 1.746664 USDC
hot / agent   0x0633a193017939Bb1eB242982397224c66948e2F
  nonce       0 -> 0      <-- the zero-gas proof
  on chain    0.000000 -> 0.000000 USDC   [native 0 wei]
  gateway     0.105500 -> 0.355500 USDC available

The org's nonce moved by two (approve, deposit). The agent's did not, its onchain balance stayed zero, and its Gateway balance grew by exactly the deposit.

One side effect. The hot key cannot deposit, withdraw or widen its own allowance, because each of those is a transaction and it has no gas for sending one. The invariant "the key that spends can never raise its own limit" is enforced by the chain.

Who pays the gas

Someone always pays. For the 402 payments themselves, it's Circle's batcher: it collects authorizations, submits one transaction per batch to the GatewayWallet, and pays its gas. In plain terms, the batcher is the gas sponsor.

This isn't a testnet perk. Circle's nanopayments docs describe the same terms on mainnet, where the product has been live since April 2026: buyers "sign payment authorizations offchain at zero gas cost", and "neither party pays per-transaction fees". The docs don't name who pays for the batch transaction itself but on Arc testnet, every batch carrying Turnstile's payments was sent by Circle's batcher.

It isn't a paymaster in the ERC-4337 sense. There, a paymaster is "a helper contract that agrees to pay for the transaction, instead of the sender itself": the user has a smart account that executes an operation on chain, and the paymaster's deposit in the EntryPoint covers the gas. Turnstile's buyer agent has no smart account and executes nothing. It signs, and the batcher submits on its behalf, much like the facilitator in plain x402.

The warm tier also pays gas so the agent doesn't have to, but only for its own transactions: one approve and one depositFor, up front. After that it never touches a payment. The job a paymaster does per operation is split in two: the org funds the agent once, and Circle's batcher pays gas on every settlement.

Arc adds a twist. It "denominates all transaction fees in USDC, the native gas token", and the native balance (18 decimals) and the USDC ERC-20 interface (6 decimals) "share the same underlying balance". Measured on an org wallet:

0xdFe3088aC34e7329006407C246C9F6D7534B2aC5
eth_getBalance   20000000000000000000   (18 dp) = 20.000000 USDC
USDC.balanceOf              20000000   ( 6 dp) = 20.000000 USDC
                 block 60938781, 2026-09-07T16:56:10Z

So a wallet with no gas has no USDC either. Turnstile's buyer agent is exactly that, 0 wei and 0 USDC on chain, and it can still pay, because its spending money doesn't sit in its wallet. It sits in the GatewayWallet, credited to its address.

Every cost in one place:

Who Pays When
Org (warm tier) Gas for approve and depositFor, plus the USDC it deposits for the agent Once, to fund the agent
Agent (hot tier) The quoted price, from its Gateway balance. No gas On every query
Circle's batcher Gas for each batch transaction On every batch
Seller Gas for withdraw(), to move its Gateway balance on chain When it cashes out

A nonce that never moves

My favourite part of this design is that you can see it from outside. eth_getTransactionCount counts the transactions an account has sent. If it is 0, the account has never broadcast one, so it has never paid a wei of gas. A balance can't show the same thing, because a wallet can receive gas money and spend it back down to zero.

The agent is 0x0633a193017939Bb1eB242982397224c66948e2F. Read at a pinned block after all eleven payments had settled and both batches had mined:

eth_getTransactionCount  0
eth_getBalance           0
USDC.balanceOf           0
                         block 60943091, 2026-09-07T17:33:02Z

Meanwhile its Gateway balance went 0.25 to 0.1055 USDC across the eleven payments. And tin the meantime the seller's Gateway balance read 0.144500 USDC, which reconciles exactly with what the buyer spent and with the eleven completed transfers.

Avoid these pitfalls

Four rough edges to know before you build on this, all observed on testnet..

  • exact means exact. The buyer's signature commits to one amount, and a seller can't settle it for anything else. I signed one authorization for 70000 atomic units ($0.07) and asked Gateway's /verify to check it against three prices: $0.07 passed (isValid: true), and both $0.035 and $0.35 failed with amount_mismatch. The reason is that EIP-3009 signs the value along with the rest of the transfer, so a different amount is a different message, and the buyer never signed it. This basically kills fallback mechanisms like: if a seller finds it can't deliver what was paid for, say the premium answer, it can't charge less on the same signature. It has to refuse, send a new 402 with the new price, and let the buyer sign again. (x402 has a separate upto scheme for amounts that vary; Gateway's nanopayments use exact.)
  • /verify does not check the payer's balance. An authorization from an empty Gateway balance verified clean and then failed /settle with insufficient_balance. An x402 seller that does its work between verify and settle (as the protocol intends) will do that work for nothing.
  • The seller's payout wallet reads zero on a block explorer, and that is correct. Gateway credits a Gateway balance. Turning it into onchain USDC is a later withdraw() the seller pays gas for. "Did they get paid?" checked on ArcScan returns a confident, wrong no.
  • Three names for one chain. eip155:5042002 on the x402 wire, arcTestnet in Circle's SDK, domain: 26 in the balances API.

An agent that can only pay

The intro asked two things of an agent with a wallet: pay amounts smaller than gas, and hold a key that can pay for queries and nothing else. Circle Gateway handles the first: the buyer signs, and Circle's batcher settles many payments in one transaction and pays the gas. Turnstile adds the rest. The org funds the agent with depositFor, so the agent never needs gas to get started.

The mandate decides whom it can pay and how much, and the agent has no client that could send a transaction. All it can do is sign for an exact amount, to a seller it's allowed to pay. How the org issues that mandate, and why raising its cap takes two signatures, is in Spending mandates for agents.

Sources


Next in the series: One payment interface, four chains, part 3 of 9.