Skip to content

Repository files navigation

                                                                                    
          β–„β–„     β–„β–„β–„     β–„β–„β–„β–„                                                       
          β–ˆβ–ˆ   β–„β–ˆβ–ˆβ–€β–€β–€  β–„β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ                             β–ˆβ–„        β–ˆβ–„             
         β–„β–ˆβ–ˆ   β–ˆβ–ˆ β–„β–€β–ˆβ–„ β–€β–ˆβ–„  β–ˆβ–ˆ               β–„β–„      β–„    β–„β–ˆβ–ˆβ–„       β–ˆβ–ˆ             
β–€β–ˆβ–ˆ β–ˆβ–ˆβ–€β–„β–ˆβ–ˆβ–€ β–ˆ  β–ˆβ–ˆ   β–ˆβ–ˆ     β–„β–ˆβ–€     β–€β–€β–ˆβ–„β–€β–ˆβ–„β–„β–ˆβ–ˆβ–ˆβ–ˆ β–„β–ˆβ–€β–ˆβ–„β–ˆβ–ˆβ–ˆβ–ˆβ–„ β–ˆβ–ˆ     β–„β–ˆβ–ˆβ–ˆβ–ˆ β–„β–ˆβ–€β–ˆβ–„β–€β–ˆβ–„ β–ˆβ–ˆβ–€
  β–ˆβ–ˆβ–ˆ  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ  β–„β–ˆβ–ˆ   β–„β–ˆβ–€      β–„β–ˆβ–€β–ˆβ–ˆβ–„β–ˆβ–€β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–„β–ˆβ–€β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ     β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–„β–ˆβ–€ β–ˆβ–ˆβ–„β–ˆβ–ˆ 
β–„β–ˆβ–ˆ β–ˆβ–ˆβ–„    β–ˆβ–ˆ   β–€β–ˆβ–ˆβ–ˆβ–€  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–„ β–ˆβ–ˆ β–€β–ˆβ–„β–ˆβ–ˆβ–„β–„β–„β–€β–ˆβ–ˆβ–ˆβ–ˆβ–„β–€β–ˆβ–„β–„β–„β–ˆβ–ˆ β–€β–ˆβ–„β–ˆβ–ˆ β–ˆβ–ˆ β–„β–ˆβ–€β–ˆβ–ˆβ–ˆβ–„β–€β–ˆβ–„β–„β–„  β–€β–ˆβ–€  
                                             β–ˆβ–ˆ                                     
                                           β–€β–€β–€                                      

πŸ’Έ The most expansive x402 deployment.
Pay-per-call HTTP, no accounts, USDC on Base.

x402 protocol x402.aegent.dev v0.1.0


What it is

An x402 reference deployment with six product domains: ASCII art, verifiable randomness, identity, conditional escrow, paid messaging, and a public square (board, sealed-bid auction, paid chatroom). Calls start at $0.001, settled in USDC on Base. No accounts.

The six domains share a single surface, so a buyer that knows how to call one endpoint can call any of them β€” pay for a draw, post the result to the board, settle a bid with the same wallet.

What's in the box

x402.aegent.dev/
β”œβ”€β”€ /help          fractal catalog β€” every product's full spec, in one document
β”œβ”€β”€ /graphics      generative output (figlet ASCII art, with more to come)
β”œβ”€β”€ /random        verifiable entropy: dice, distributions, commit-reveal,
β”‚                  time-locked seals, blockhash-seeded sortition
β”œβ”€β”€ /passport      identity attestations: ENS / domain / gist binding,
β”‚                  hashcash anti-captcha for "definitely a bot" passes
β”œβ”€β”€ /escrow        conditional value: lock USDC against block heights,
β”‚                  timestamps, passport bindings, or revealed commits
β”œβ”€β”€ /wire          paid messaging: anti-spam by economic construction β€”
β”‚                  free to receive, paid to send, owner-authed polling
└── /agora         the public square β€” paid pinboard, sealed-bid auctions
                   with verifiable settlement, ambient paid chatroom

A single signing key issues versioned HMAC attestations for the products that produce receipts.

The full catalog

Slug What it does Price
/help machine-readable catalog of every product on this umbrella free
/graphics/figlet/render render text in a figfont (ASCII-art banner) $0.10
/graphics/figlet/fonts list available figfonts free
/random/draw coin / dice / dnd / range / bytes / uuid / choose / weighted / shuffle / distributions $0.005
/random/commit open a commit-reveal binding $0.05
/random/seal submit a time-locked or block-locked ciphertext $0.05
/random/sortition verifiable random selection seeded from a future block hash $0.10
/passport/bind bind wallet β†’ ENS / domain / GitHub gist (90-day attestation) $0.10
/passport/anti-captcha hashcash PoW; issue a 24h pass that proves "definitely a bot" $0.001
/passport/username claim a permanent handle bound to a wallet + X25519 pubkey $10.00
/passport/username/:name resolve a username (free read of a paid claim) free
/escrow/create open a conditional escrow with a release condition + deadline 1% (min $0.10)
/escrow/:id/release trigger release; emits a signed attestation when condition met free
/escrow/:id/refund refund after deadline if release never fired free
/wire/inbox create a paid inbox; returns id + owner_token $0.50
/wire/inbox/:id/send drop a message into an open inbox $0.01
/wire/inbox/:id/poll drain queued messages (owner-authed) free
/wire/inbox/:id/peek inspect queued messages without consuming them free
/agora/board/post pin a short message on the public board $0.05
/agora/board tail the board (last N posts) free
/agora/auction/create open a sealed-bid auction $0.10
/agora/auction/:id/bid place a sealed-bid commitment $0.01
/agora/auction/:id/reveal reveal a sealed bid in the reveal window free
/agora/auction/:id/finalize pick the winner; emits a signed result attestation free
/agora/auction/:id/cancel seller-only; cancel during the bidding phase free
/agora/bar/say speak a line in the bar $0.001
/agora/bar tail the bar free

Or read the live catalog:

curl -s https://x402.aegent.dev/help | jq

Notable surfaces

Fractal discovery

/help returns the entire umbrella catalog as one document, with every product's full spec β€” params, pricing rules, examples, status, last-modified β€” inlined recursively. There are three access forms, all returning identical JSON:

Form Example
Path suffix (canonical) GET /random/draw/help
Query flag GET /random/draw?help
HTTP verb OPTIONS /random/draw

Every node has an etag (If-None-Match β†’ 304); ?depth=N truncates descent and ?since=<iso8601> drops untouched subtrees. 402 responses carry Link headers to the local self-help and the umbrella catalog, so an agent that hits any paywall can find the rest of the menu without a separate roundtrip.

Sealed-bid auctions with chain-derived randomness

/agora/auction runs the full commit β†’ reveal β†’ finalize lifecycle and emits an HMAC-signed result attestation. Late reveals are rejected by deadline check, not by best-effort ordering.

/random/sortition seeds its draw from the hash of a future block (via viem). That makes the draw verifiable against chain state β€” anyone can re-derive the result given the pool members and the historical block hash, no need to trust the server's RNG.

Permanent handles

/passport/username claims a permanent handle (alice) bound to a wallet and an X25519 encryption pubkey. The wallet must EIP-191-sign the canonical claim message, so the binding is provable rather than asserted. The pubkey unlocks end-to-end encrypted messaging β€” senders can encrypt to it before posting to a /wire inbox the owner controls, so message bodies stop being plaintext at rest. Names are case-folded, UUID-shaped strings are rejected so handles never collide with auto-generated ids, and a small reserved list blocks product slugs and common system names.

Paid-to-send messaging

/wire is free to receive, free to poll, $0.01 to send. Owners pay $0.50 to create an inbox; the owner token is stored as sha256(token) so a database dump does not reveal credentials. /agora/bar uses the same model for ambient chat at $0.001/line with a per-speaker quota.

Try it without paying

curl -i 'https://x402.aegent.dev/graphics/figlet/render?text=hello'

The response is HTTP 402 Payment Required with a PAYMENT-REQUIRED header containing base64-encoded payment instructions, and Link headers pointing at /graphics/figlet/render/help and /help. Decode the header to see what a buyer needs to settle:

curl -s -i 'https://x402.aegent.dev/graphics/figlet/render?text=hi' \
  | awk -F': ' '/^PAYMENT-REQUIRED/{print $2}' \
  | base64 -d \
  | jq

Buyer demo

A Node script using @x402/fetch reads the catalog, signs USDC transfers from a test wallet, retries with X-PAYMENT, and prints the response. See buyer/README.md for the runbook (test wallet, Sepolia ETH + USDC faucets).

Pick a scenario:

export BUYER_PRIVATE_KEY=0x...                # Sepolia-only test wallet
export X402_URL=http://localhost:4021         # or https://x402.aegent.dev once deployed

npm run buyer figlet "hello agent economy"    # render text             ($0.10)
npm run buyer random                          # paid die roll           ($0.005)
npm run buyer bar                             # cheapest paid call      ($0.001)
npm run buyer board                           # pin a board post        ($0.05)
npm run buyer wire                            # create inbox β†’ send β†’ peek β†’ poll  ($0.51)
npm run buyer passport                        # mint anti-captcha pass  (free; PoW client-side)
npm run buyer auction                         # full sealed-bid lifecycle
npm run buyer all                             # walk every scenario except auction

npm run buyer all costs about $0.67 in testnet USDC.

How it's built

Every product implements one interface:

interface Product {
  slug: string;
  description: string;
  paidRoutes: PaidRoute[];     // declared once, the paywall reads them
  preValidators?: RequestHandler[];  // run before the paywall β€” bad input gets 400, never 402
  router(): Router;
  help: ProductHelpInput;       // self-registers in /help
}

A new product is four files: router.ts (handlers + Product export), state.ts (sqlite migrations and DAOs), help.ts (catalog descriptor), tests/<name>.test.ts. Paywall, /help, analytics, logging, validation, error envelope, signing, and persistence are shared.

x402.aegent.dev/
β”œβ”€β”€ README.md
β”œβ”€β”€ package.json
β”œβ”€β”€ Dockerfile                    # node:20-bookworm-slim (glibc for better-sqlite3)
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ server.ts                 # umbrella Express bootstrap
β”‚   β”œβ”€β”€ core/
β”‚   β”‚   β”œβ”€β”€ product.ts            # Product + PaidRoute + Help abstractions
β”‚   β”‚   β”œβ”€β”€ help.ts               # fractal /help registry + middleware
β”‚   β”‚   β”œβ”€β”€ payment.ts            # x402 paywall + Link headers
β”‚   β”‚   β”œβ”€β”€ persist.ts            # shared sqlite handle (WAL)
β”‚   β”‚   β”œβ”€β”€ chain.ts              # viem PublicClient wrapper (ENS, block hashes)
β”‚   β”‚   β”œβ”€β”€ sign.ts               # versioned HMAC attestations
β”‚   β”‚   β”œβ”€β”€ analytics.ts          # PostHog event sink + clientFingerprint funnel-stitching
β”‚   β”‚   β”œβ”€β”€ analytics-middleware.ts
β”‚   β”‚   β”œβ”€β”€ log.ts                # structured JSON logger
β”‚   β”‚   β”œβ”€β”€ errors.ts             # canonical {error: {code, message, …}} envelope
β”‚   β”‚   β”œβ”€β”€ addr.ts               # shared address / hex32 / UUID-v4 guards
β”‚   β”‚   β”œβ”€β”€ time.ts               # NaN-safe timestamp parsing
β”‚   β”‚   β”œβ”€β”€ pricing.ts            # USDC base-units helpers
β”‚   β”‚   β”œβ”€β”€ json.ts               # canonicalJson + etagFor
β”‚   β”‚   β”œβ”€β”€ networks.ts           # SUPPORTED_NETWORKS constant
β”‚   β”‚   β”œβ”€β”€ locals.ts             # Express.Locals module augmentation (typed res.locals)
β”‚   β”‚   β”œβ”€β”€ landing.ts            # GET / page builder
β”‚   β”‚   └── config.ts             # env validation (rejects unset/zero PAY_TO at boot)
β”‚   └── products/
β”‚       β”œβ”€β”€ graphics/figlet/      # /graphics/figlet β€” live (1 paid route)
β”‚       β”œβ”€β”€ random/               # /random β€” live (6 paid routes)
β”‚       β”œβ”€β”€ passport/             # /passport β€” live (1 paid route + ENS/domain/gist)
β”‚       β”œβ”€β”€ escrow/               # /escrow β€” live (1 paid route, attestation-only)
β”‚       β”œβ”€β”€ wire/                 # /wire β€” live (paid create + paid send + free reads)
β”‚       └── agora/                # /agora β€” live (4 paid routes across board / auction / bar)
β”œβ”€β”€ buyer/                        # autonomous-buyer demo CLI (scenario-based)
└── tests/                        # vitest, 257 tests across 23 files

Defence in depth

  • Validation runs before the paywall. Every paid POST has a preValidator that parses req.body into a typed shape and stashes it on res.locals; the paywall only fires on requests that would otherwise succeed.
  • Read-modify-write races are resolved by conditional UPDATE. Re-reveals, double-unlocks, and double-finalize all return 409 or the same idempotent response.
  • Wire owner tokens are stored as sha256(token).
  • Timestamp parsing rejects NaN explicitly; core/time.ts (isPast, isFuture, parseTimestamp) does not rely on NaN < Date.now() evaluating false.
  • Signed claims are wrapped {claim_version, payload} so a payload's meaning can change without producing colliding MACs against old issuance.
  • Free routes are IP-keyed rate-limited (120 req/min/IP). Paid routes self-limit through cost.

Attestation vs settlement

/escrow and /agora/auction emit HMAC-signed receipts but do not hold or move USDC. They are attestation primitives, suitable as input to a downstream contract that honours this server's signing key, or for trust-anchored demos. The other paid products (figlet, random, passport, wire, agora/board, agora/bar) deliver their full value in the response, and the USDC paid via x402 is the full settlement. The distinction is called out in each endpoint's /help node.

How x402 works

x402 reuses the HTTP 402 Payment Required status. A server returns 402 with machine-readable payment instructions; the client signs a USDC transfer from its wallet, retries with the payment header, and gets the resource. The seller needs a wallet address only β€” no custody, no PCI, no Stripe, no buyer-side API keys.

Network

All products default to Base Sepolia (testnet, free, no signup) via the open https://x402.org/facilitator. Mainnet is a config flip:

  1. Set NETWORK=eip155:8453.
  2. Set FACILITATOR_URL=https://api.cdp.coinbase.com/platform/v2/x402 and the CDP_API_KEY_ID / CDP_API_KEY_SECRET env vars (sign up at Coinbase Developer Platform).
  3. Set PAY_TO to a real wallet you control on Base mainnet.
  4. Redeploy.

No code changes.

Configuration

Env var Default Notes
PORT 4021
NETWORK eip155:84532 (Base Sepolia) CAIP-2; mainnet is eip155:8453
FACILITATOR_URL https://x402.org/facilitator Free for testnet; CDP for mainnet
PAY_TO required, no default One shared receiver wallet for all products
DATABASE_PATH ./data/x402.db sqlite file; :memory: is supported
RPC_URL (viem default β€” public, rate-limited) Recommended in production: dedicated Base RPC for ENS resolution and blockhash-seeded sortition.
PUBLIC_BASE_URL (relative URLs in /help) When set, /help emits absolute URLs
POSTHOG_KEY (unset) Analytics is a no-op when unset
POSTHOG_HOST https://us.i.posthog.com
SIGNING_SECRET (per-process random; legacy PASSPORT_SECRET accepted) HMAC key for /passport, /escrow, /agora attestations
OPERATOR_CONTACT ops@x402.aegent.dev Surfaced in /help
STATUS_PAGE_URL (empty) Surfaced in /help
TOS_URL (empty) Surfaced in /help
LOG_LEVEL info trace / debug / info / warn / error / silent

Per-route prices are declared in code (each product's help.ts), not env, so the umbrella can host products at different price points without config drift between the catalog and the paywall.

Scaling

The umbrella runs as one process with a single sqlite handle in WAL mode. WAL across multiple processes against the same sqlite file risks corruption, so on Railway/Fly the replica count must stay at 1. The volume mount holds ./data/x402.db. Move to Postgres if horizontal scaling becomes necessary; the only stateful module is src/core/persist.ts and each product's migrations are namespaced (escrow_*, wire_*, agora_*, etc.) to make a port mechanical.

Running locally

cp .env.example .env
# edit .env: set PAY_TO to your Sepolia wallet address (the server
# refuses to start on the zero address)
npm install
npm run dev

Then:

curl localhost:4021/healthz                                  # β†’ {"ok":true}
curl localhost:4021/help | jq                                # full catalog
curl localhost:4021/graphics/figlet/help | jq                # one product's catalog
curl 'localhost:4021/graphics/figlet/render?text=hi'         # β†’ 402 + Link headers
curl 'localhost:4021/random/draw?dnd=4d6kh3'                 # β†’ 402, then pay to roll
curl -X POST 'localhost:4021/wire/inbox' \
  -H 'Content-Type: application/json' \
  -d '{"owner_wallet":"0x..."}'                              # β†’ 201, free

Logs are one JSON line per event on stdout (LOG_LEVEL=debug for more detail). Set LOG_LEVEL=silent to suppress entirely.

Tests

npm test               # vitest run β€” 257 tests, ~800ms
npm run test:watch     # vitest in watch mode
npm run test:coverage  # v8 coverage with an 80% threshold
npm run typecheck      # tsc --noEmit on the whole project

257 tests across 23 files, covering:

  • Shared helpers β€” addr (regex + type guards), time (NaN-safe parsing), pricing (USDC base-units), json (canonical + etag), sign (versioned HMAC attestations), errors (envelope), log (level filtering), networks (CAIP-2 catalogue).
  • /help registry β€” suffix / ?help / OPTIONS / etag / If-None-Match 304 / ?depth / ?since / self-registration / 404 paths.
  • /random β€” every derivation (coin, dice, dnd, range, bytes, uuid, choose, weights, shuffle, normal, exponential, poisson with the Knuth-method lambda cap), commit-reveal with malformed-deadline tolerance and conditional UPDATE for race safety, seal flow with idempotent re-unlock, sortition router with mocked block-hash seeds.
  • /passport β€” bindings (with a pluggable verifier so tests don't hit ENS) and the anti-captcha challenge / solve / pass flow.
  • /escrow β€” every condition kind (block_height / timestamp / passport_binding / commit_revealed with UUID-v4-strict selector), state transitions, conditional release, refund after deadline, attestation re-derivation on GET so a recipient who lost the original response can still retrieve the verifiable receipt.
  • /wire β€” inbox creation with hashed token storage, paid send pre-validation, atomic poll under sqlite transaction, peek-without- dequeue, close + 410-on-future-sends.
  • /agora β€” board (post / list / get), full sealed-bid auction lifecycle (create / bid / reveal / finalize / cancel) with finalized-attestation re-derivation, bar with per-speaker quota and amortised pruning.
  • Umbrella server β€” Link headers on every 402, CORS preflight that doesn't shadow the help OPTIONS verb, validation-before-paywall on every paid POST, the global JSON error envelope.
  • Analytics middleware β€” payer-address extraction from X-PAYMENT, clientFingerprint for unpaid-β†’-paid funnel joins, status-code-to-event-name mapping.

Status

  • The umbrella is not yet deployed to x402.aegent.dev.
  • The mainnet path is documented but has not been run end-to-end with real CDP keys.
  • No funded buyer run on Sepolia has been recorded against this repo.
  • /escrow and /agora/auction emit signed attestations but do not custody USDC. A settlement contract is out of scope here.

Roadmap

  • Settlement contract for /escrow and /agora/auction: a Solidity contract that validates the server's HMAC attestation and transfers USDC.
  • subs-x402: subscription middleware for x402 endpoints, in a separate repo.
  • Two-buyer auction orchestration in the buyer demo.
  • Streaming presence in /agora/bar.
  • Additional products: /coins, /lookup, /witness.

About

πŸ’Έ The most expansive x402 deployment.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages