x402 · USDC on Base + Solana

x402-shipping

Rate-shop every carrier and buy the label in two paid calls — the label PDF comes back base64 in the response body.

git clone https://github.com/nirholas/x402-shipping
cd x402-shipping && npm install
npm run dev   # http://localhost:4023

Pricing

RoutePriceWhat you get back
POST /rates$0.003the full carrier rate table with prices, transit days, and `cheapest` / `fastest` flags
POST /label$0.02the label as base64 PDF plus tracking number, tracking URL, carrier, service and the amount charged
GET /healthfreeLiveness, data source, configured rails
GET /.well-known/x402freeMachine-readable discovery manifest

Every paid route returns the purchased artifact in the 200 body — nothing is deferred to a webhook or a second fetch.

How a paid call works

1 · RequestAgent calls POST /rates
2 · 402Server quotes the price on both rails
3 · PayClient signs on its rail, retries with X-PAYMENT
4 · 200Artifact in the body + settlement receipt header

Two rails, one challenge

Pay in USDC on Base or Solana — your client picks. The 402 body always lists both.

EVM · Base

USDC on base-sepolia (or base). The wallet signs an EIP-3009 authorization locally — no server round-trip.

0x40252CFDF8B20Ed757D61ff157719F33Ec332402

Solana

USDC SPL transfer on solana. A facilitator sponsor pays the SOL network fee, so buyers need only USDC. Helpers at /api/x402-checkout build and wrap the transaction.

WwwuGbqHrwF5RG89KhUbmRWEvjnRH9k5kVM5p7T3WwW

Quickstart for agents

import { writeFileSync } from "node:fs";
import { privateKeyToAccount } from "viem/accounts";
import { wrapFetchWithPayment } from "x402-fetch";

const payFetch = wrapFetchWithPayment(fetch, privateKeyToAccount(process.env.PRIVATE_KEY));
const shipment = { from, to, parcel };

// Rate-shop — $0.003
const { rates, cheapest } = await (await payFetch("http://localhost:4023/rates", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify(shipment),
})).json();

// Buy the cheapest — $0.02
const label = await (await payFetch("http://localhost:4023/label", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ rateId: cheapest.rateId, ...shipment }),
})).json();

writeFileSync("label.pdf", Buffer.from(label.labelPdfBase64, "base64"));
console.log(label.trackingNumber);   // the label is already on disk

Live carrier data is used when `SHIPPO_API_TOKEN` or `EASYPOST_API_KEY` is set — both providers issue free test-mode keys. Without either, the service quotes from a deterministic fixture rate table (seven real carrier services: USPS Ground Advantage / Priority / Priority Express, UPS Ground / 2nd Day Air, FedEx Home Delivery / 2Day) priced on actual weight, volume and ZIP-derived zone, and issues a genuine one-page PDF label. Every response carries `"source": "fixture"` and `"testMode": true`.

Links