@a3stack/payments

Payments

Agent-to-agent payments via x402 protocol. Charge for your services or pay other agents. USDC on Base, gasless for the payer.

How x402 works

x402 is an HTTP protocol for micropayments. When a client requests a paid resource, the server responds with HTTP 402 (Payment Required) plus payment details. The client signs an EIP-3009 authorization (a gasless USDC transfer) and retries. A facilitator settles on-chain.

Key feature: The payer never sends ETH or gas. EIP-3009 is a signed authorization — the facilitator pays gas and collects a small fee. Instant, cheap, and trustless.

PropertyValue
TokenUSDC (Circle)
NetworkBase mainnet (eip155:8453)
SettlementVia facilitator (gasless for payer)
Protocolx402 v2 (CAIP-2 network IDs)
SigningEIP-3009 (signed USDC transfer authorization)
Min amount1 base unit = 0.000001 USDC

USDC amount encoding

USDC has 6 decimal places. Amounts are always in base units (strings):

"1000"= 0.001 USDC
"10000"= 0.01 USDC
"100000"= 0.10 USDC
"1000000"= 1.00 USDC

Payment Client (paying agents)

typescript
import { createPaymentClient } from "@a3stack/payments";
import { privateKeyToAccount } from "viem/accounts";

const account = privateKeyToAccount(process.env.PRIVATE_KEY);

const payer = createPaymentClient({
  account,
  chains: ["eip155:8453"],  // supported chains (Base default)
  // maxAmount: "1000000",  // optional: max USDC to auto-pay (safety cap)
});

payer.fetch()

Auto-pays x402 requirements. Drop-in replacement for fetch.

typescript
// Auto-pays x402 requirements transparently
const response = await payer.fetch("https://api.paidagent.ai/tool");
const data = await response.json();

// Or target a specific agent by ERC-8004 ID
// (auto-resolves payment wallet from on-chain registration)
const paidFetch = await payer.fetchForAgent("eip155:8453:0x8004...#2376");
const response = await paidFetch("https://mcp.paidagent.ai/tool");

payer.getBalance()

typescript
// Check your USDC balance on Base
const balance = await payer.getBalance("eip155:8453");
// {
//   amount: 1500000n,      // raw USDC (6 decimals)
//   formatted: "1.500000", // human-readable
//   symbol: "USDC",
// }

payer.decodeReceipt()

typescript
// After a paid request, decode the payment receipt
const receipt = payer.decodeReceipt(response);
// {
//   txHash?: "0x...",
//   from: "0x...",
//   to: "0x...",
//   amount: "1000",
//   asset: "0x833589...", // USDC
//   chain: "eip155:8453",
//   timestamp: 1733000000,
// }

payer.checkPaymentRequirements()

typescript
// Check if an endpoint requires payment before connecting
const check = await payer.checkPaymentRequirements("https://api.agent.ai/tool");
// {
//   requiresPayment: true,
//   amount: "1000",
//   asset: "0x833589...",
//   network: "eip155:8453",
//   payTo: "0x1be93C..."
// }

Payment Server (receiving agents)

typescript
import { createPaymentServer } from "@a3stack/payments";
import { USDC_BASE } from "@a3stack/payments";

const receiver = createPaymentServer({
  payTo: "0x1be93C...",     // your wallet — receives USDC
  amount: "100000",          // 0.10 USDC in base units (6 decimals)
  asset: USDC_BASE,          // USDC address on Base
  chain: "eip155:8453",
  description: "My AI tool, 0.10 USDC per call",
  // facilitator?: "https://...",  // optional custom facilitator URL
});

receiver.middleware()

Express.js middleware. Verifies payment before your handler runs.

typescript
import express from "express";
const app = express();

app.use("/tool", receiver.middleware(), (req, res) => {
  // ✓ Payment verified at this point
  const payment = res.locals.payment; // PaymentDetails

  console.log(`Received ${payment.amount} USDC from ${payment.from}`);

  res.json({ result: "premium content" });
});

app.listen(3000);

Manual verification

typescript
// Manual: check if a request carries valid payment
const { valid, payment, error } = await receiver.verify(req);
if (!valid) {
  const reqs = receiver.buildRequirements("https://api.myagent.ai/tool");
  res.status(402).set("X-PAYMENT-REQUIRED", reqs).end();
  return;
}

// Payment valid — serve the response

Direct class usage

typescript
import { PaymentClient } from "@a3stack/payments";

// Direct class instantiation (same as createPaymentClient)
const client = new PaymentClient({ account });
const balance = await client.getBalance("eip155:8453");

Constants

typescript
import { USDC_BASE, USDC_ETHEREUM, USDC_ARBITRUM } from "@a3stack/payments";

// Base mainnet USDC
console.log(USDC_BASE);
// "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"

Protocol flow (annotated)

text
// The x402 payment flow in detail:

// 1. Client makes a normal HTTP request
GET /tool HTTP/1.1

// 2. Server returns 402 with payment requirements
HTTP/1.1 402 Payment Required
X-PAYMENT-REQUIRED: { "accepts": [{ "scheme": "exact", "network": "eip155:8453", ... }] }

// 3. Client signs EIP-3009 authorization (gasless — no ETH needed, just USDC)
// Authorization: Transfer USDC from client to agent's payTo address

// 4. Client retries with X-PAYMENT header
GET /tool HTTP/1.1
X-PAYMENT: <base64-encoded EIP-3009 signature>

// 5. Server verifies signature and responds
HTTP/1.1 200 OK
X-PAYMENT-RESPONSE: { "success": true, "txHash": "..." }

// 6. Facilitator settles the USDC transfer on-chain
// Client: -0.001 USDC   Agent: +0.001 USDC

Key addresses

ContractAddressChain
USDC0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913Base
Permit20x000000000022D473030F116dDEE9F6B43aC78BA3Base