Pay-Per-Call LLM API with Circle USDC Nano Payments

Thursday, April 09, 2026 by kimoisteve
Pay-Per-Call LLM API with Circle USDC Nano Payments

Pay-Per-Call LLM API with Circle USDC Nano Payments

Introduction

Every AI API on the internet today follows the same billing model:

Sign up  ──►  Enter credit card  ──►  Pick a plan  ──►  Billed monthly

That model works for humans. It completely breaks for AI agents.

An autonomous agent making thousands of API calls per session cannot sign up for subscriptions or hold credit cards. It needs to pay per action, programmatically, with no human in the loop. This is the gap that Circle's Gateway Nano Payments solve.

With Circle Gateway, the payment flow looks like this instead:

Fund wallet  ──►  Call the API  ──►  $0.01 USDC deducted  ──►  Response delivered

No accounts. No monthly invoices. No wallet pop-ups. The payment is embedded in the HTTP request itself.

In this tutorial you will build a working pay-per-call LLM API. A user (or agent) sends a message, the server demands $0.01 USDC, the buyer pays automatically using a gasless offchain signature, and the LLM responds. You will also build a React demo UI that visualizes the full payment flow in real time.

This project was built for the Arc Agentic Economy Hackathon powered by Circle. The full source code is at github.com/Stephen-Kimoi/nanopayments-with-usdc.

What you will build

  • An Express server (the seller) that protects a /chat endpoint with Circle Gateway payment middleware. One line of code to require payment.
  • A GatewayClient (the buyer) that automatically handles the 402 → sign → settle → response cycle
  • A React + TypeScript demo UI showing the five-step payment pipeline animating in real time, with a receipt card after each successful call
  • Two demo endpoints (/demo/balance, /demo/chat) so the UI can trigger real payments without exposing private keys in the browser

Prerequisites

  • Node.js 18+ and npm
  • An EVM wallet with USDC on Base Sepolia (the buyer wallet)
  • An EVM address to receive payments (the seller address; can be the same wallet)
  • OpenAI API key for the LLM backend
  • Basic familiarity with TypeScript and Express

Get testnet USDC

If you don't have USDC on Base Sepolia yet, you need two things: testnet ETH for gas, and testnet USDC to pay with.

Testnet ETH (for gas on deposits):

Testnet USDC:


Quick Start: Get It Running First

Let's get the project running before explaining how it works. Clone the repo, install dependencies, configure your environment, and start both the server and the UI.

Clone and install

git clone https://github.com/Stephen-Kimoi/nanopayments-with-usdc.git
cd nanopayments-with-usdc
npm install
cd ui && npm install && cd ..

Configure environment

Copy the example file and fill in your values:

cp .env.example .env

Open .env and set:

# Your wallet address — receives $0.01 USDC on each call
EVM_ADDRESS=0xYourSellerWalletAddressHere

# Your private key — the buyer wallet that pays per call.
# The server uses this internally; the browser never touches it.
EVM_PRIVATE_KEY=0xYourBuyerPrivateKeyHere

# OpenAI
OPENAI_API_KEY=sk-...
OPENAI_MODEL=gpt-4o-mini

# Server URL (leave as-is for local dev)
RESOURCE_SERVER_URL=http://localhost:4021

Deposit USDC into your Circle Gateway Wallet

Before the first payment can go through, the buyer wallet needs USDC deposited into the Circle Gateway Wallet. This is a one-time setup step. The Gateway Wallet is a smart contract at 0x0077777d7EBA4688BDeF3E311b846F25870A19B9 (same address on all EVM testnets) that holds your balance for gasless payments.

Per Circle's Gateway documentation, depositing funds moves USDC from your regular wallet into this contract so Circle can later settle signed payment authorizations on your behalf without requiring you to submit individual transactions.

The deposit script lives at client/deposit.ts. The core of it is client.deposit(depositAmount) at line 42, which under the hood sends two onchain transactions: an ERC-20 approve so the Gateway contract can pull your USDC, then a deposit call into the contract. The balance check before and after (lines 30–32 and 46–47) lets you confirm the funds moved.

Run the deposit:

npm run deposit -- 0.10

Base Sepolia takes 13 to 19 minutes for Circle to credit the deposit after the onchain transaction confirms. Poll until the balance appears:

npm run balance

The balance polling script at client/check-balance.ts calls client.getBalances() every 60 seconds and exits when the Gateway balance goes above zero. When you see the Gateway balance go above 0, you are ready.

Start the server and UI

Open two terminals:

# Terminal 1: Express server on port 4021
npm run server

# Terminal 2: Vite dev server on port 5173
npm run ui

Open http://localhost:5173. Type a question, hit Send, and watch the payment flow animate.

NanoAI demo UI showing the 5-step payment pipeline and chat interface
The live demo UI. The right panel shows each step of the payment flow animating in real time.

How the Demo Works

When you hit Send in the UI, here is exactly what happens at the code level.

Step 1: Request sent

The send() function in ui/src/App.tsx (lines 235–253) fires. It advances the pipeline animation to step 1 and posts the full conversation history to POST /demo/chat. This is a demo-only wrapper endpoint on the Express server — the browser never touches private keys.

Step 2: 402 Payment Required

Inside /demo/chat (server/index.ts lines 59–82), the server's embedded buyerClient calls the real POST /chat. That route is protected by gateway.require("$0.01") at lines 35–38, so the first call comes back as HTTP 402 with machine-readable payment requirements: the price, the network, and the payment scheme.

Step 3: Sign authorization

GatewayClient.pay() intercepts the 402 automatically. It reads the payment requirements and creates an EIP-3009 TransferWithAuthorization signature — a cryptographic message that authorizes Circle to move $0.01 USDC from the buyer's Gateway Wallet to the seller. This happens offchain: no blockchain transaction, no gas fee. The SDK import that powers this is at line 5 of server/index.ts.

Step 4: Circle Gateway settle

GatewayClient.pay() retries the original request with the signature attached as a header. The createGatewayMiddleware at lines 35–38 forwards the signature to Circle's settle API, which validates it and queues the transfer. Circle batches these offchain signatures and settles them onchain periodically — this is why there is no gas per call.

Step 5: Response delivered

Once payment clears, the middleware calls next() and the /chat handler (lines 118–142) runs, calls OpenAI, and returns the reply. Back in /demo/chat, the server snapshots the balance before and after the payment and builds the receipt at lines 84–100. The UI receives it all at lines 256–264 of App.tsx, advances the pipeline to step 5, then renders the reply and the receipt card.

The whole cycle from the first request to the LLM response takes about two seconds.


The Code Explained

Seller side: one line to require payment

The server is a standard Express app with Circle's Gateway middleware. See the full file at server/index.ts.

createGatewayMiddleware is imported and configured at lines 4 and 35–38. It takes your seller address and the target network (eip155:84532 for Base Sepolia). From there, the entire payment gate for the /chat route is a single middleware call: gateway.require("$0.01"), applied at line 118.

If the request arrives without a valid payment proof, the middleware returns 402 automatically. If the payment is valid, it populates req.payment with the payer address, amount, and network, then calls next(). The OpenAI call inside the handler at lines 128–134 only runs after the payment has cleared.

Buyer side: automatic payment handling

The standalone buyer client is at client/pay.ts. It initializes a GatewayClient at lines 26–29, checks that the Gateway balance is above the $0.01 threshold at lines 36–39, and then calls client.pay() at lines 46–59. That single pay() call handles the entire flow — send request, receive 402, sign EIP-3009 offchain, retry with the signature, get the response — without the caller knowing any payment happened.

Private keys exported without a 0x prefix are normalized automatically at lines 17–19, so the client works regardless of how your wallet exports keys.

Demo endpoints for the UI

The UI calls /demo/balance and /demo/chat. These are wrapper endpoints on the same Express server that embed their own GatewayClient instance, so the browser never needs a private key.

The embedded buyer client is initialized at lines 18–26 of server/index.ts using the same EVM_PRIVATE_KEY environment variable. The /demo/balance endpoint at lines 42–57 returns live gateway and wallet balances for the header. The /demo/chat endpoint at lines 59–105 snapshots the balance before calling buyerClient.pay(), calls the real /chat route (which demands payment), snapshots the balance after, and returns the reply alongside the full payment receipt.


How an AI Agent Would Actually Use This

The seller code above could be deployed to any public URL, for example https://nanoai.example.com/chat. An AI agent that needs to call it would run essentially the same code as client/pay.ts — initialize a GatewayClient with a funded private key, then call client.pay() with the endpoint URL and the message payload. That is the entire integration.

The agent doesn't sign up for an account, doesn't hold an API key, and doesn't have a subscription. It has USDC in a Gateway Wallet and pays per call. The operator of the AI service receives USDC in real time, per request, with no billing infrastructure to maintain.

An orchestrator framework like LangChain, AutoGen, or a custom agent loop would wrap outbound HTTP calls with GatewayClient.pay(). When the agent picks "call the LLM tool," the payment happens transparently inside the tool implementation. The agent never needs to know payment is involved.


Why This Matters: The Economics of Nano Payments

Traditional payment rails carry a minimum viable transaction cost of roughly $0.30 (Stripe's fixed fee alone). That floor makes anything under about $1 per call economically unworkable — you would pay more in fees than the service is worth. So every AI API today is forced into subscriptions or credit bundles.

Circle Gateway removes that floor entirely:

FeatureTraditional API billingCircle Gateway Nano Payments
Minimum viable price~$1+ per call$0.001 per call
Gas per paymentN/A (card fees apply)$0.00
Human required to set upYes (account, card, plan)No
Autonomous agent compatibleNoYes
Payment settledMonthly invoiceNear real-time, batched onchain

The $0.01/call price in this project is arbitrary. You could charge $0.001, $0.005, or $0.10. The payment mechanics work at any of those price points. For an agent making 10,000 calls per day, the difference between a flat monthly subscription and paying exactly for what you use is enormous, especially when usage patterns are unpredictable.


The Payment Scheme: GatewayWalletBatched

Circle's scheme is called GatewayWalletBatched. It is built on EIP-3009 (transferWithAuthorization), which allows an authorized party to transfer tokens on a holder's behalf using a cryptographic signature, without the holder submitting a transaction.

The flow in detail:

  1. Deposit — the buyer deposits USDC into the Gateway Wallet smart contract (0x0077777d7EBA4688BDeF3E311b846F25870A19B9 on all EVM testnets). See client/deposit.ts.
  2. Sign — for each payment, the buyer signs an EIP-3009 message offchain: "I authorize Circle to transfer $X from my gateway balance to seller Y, valid between timestamp A and B."
  3. Submit — Circle collects these signatures and settles them onchain in batches. Each individual call costs zero gas because it is the batch that goes onchain, not each individual transfer.
  4. Receive — the seller's address receives USDC onchain, net of any Circle fees.

The key insight is that the signature is the payment proof. The middleware can verify a signature instantly without waiting for a blockchain transaction. Settlement happens asynchronously in the background.


Project Structure

nanopayments-with-usdc/
├── server/
│   └── index.ts          # Express seller + embedded buyer for /demo/* routes
├── client/
│   ├── pay.ts            # Standalone buyer client (run from terminal)
│   ├── deposit.ts        # One-time USDC deposit into Gateway Wallet
│   └── check-balance.ts  # Poll balance until deposit is credited
├── ui/
│   ├── src/
│   │   ├── App.tsx       # Full React demo UI (all components inline)
│   │   └── index.css     # Tailwind + custom animations
│   └── vite.config.ts    # Vite config, proxies /demo/* to port 4021
├── .env.example
└── package.json

Testing Without the UI

You can also test the payment flow directly from the terminal using the standalone client:

npm run client

Output:

Gateway balance : 0.09 USDC
Wallet USDC     : 2.60 USDC

Calling : POST http://localhost:4021/chat
Prompt  : Explain what Circle USDC nano payments are in two sentences.

Status : 200
Reply  : Circle USDC nano payments enable micropayments at the HTTP layer...
Model  : gpt-4o-mini

Gateway balance after : 0.08 USDC

The server logs the incoming payment on its side:

Payment received: 10000 USDC units from 0xYourBuyerAddress on eip155:84532

Common Issues

Gateway balance shows 0 after deposit. Base Sepolia takes 13 to 19 minutes for Circle to credit a deposit after the onchain transaction confirms. Run npm run balance to poll every 60 seconds until it appears. See client/check-balance.ts.

"PRIVATE_KEY is required" error. Make sure your .env has EVM_PRIVATE_KEY set. The client reads PRIVATE_KEY or EVM_PRIVATE_KEY and normalizes it to include the 0x prefix automatically. See lines 17–19 of client/pay.ts.

Server returns 402 repeatedly. The client's Gateway balance is 0 or below the $0.01 threshold. Deposit more USDC and wait for Circle to credit it.

/demo/chat returns 503. The server could not find a buyer private key in the environment. Check that EVM_PRIVATE_KEY is set and restart the server after editing .env. See lines 18–26 of server/index.ts.


Next Steps

  • Deploy the server to a public URL (Railway, Render, or a VPS). Any agent with a funded Gateway Wallet can use it immediately, no sign-up required.
  • Change the price. Try gateway.require("$0.001") for sub-cent calls, or gateway.require("$0.10") for heavier models.
  • Protect multiple endpoints. Each route can have a different price: gateway.require("$0.01") for a fast model, gateway.require("$0.05") for a larger one.
  • Add the buyer to an agent framework. Wrap GatewayClient.pay() as a LangChain tool or MCP tool and drop it into any agent that needs LLM access.
  • Mainnet. The same code works on Base mainnet by changing chain: "base" and networks: ["eip155:8453"].

Resources