# 402-mcp — Full Technical Reference

> For a concise overview, see [llms.txt](llms.txt). This document provides the complete technical reference for integrating with, building on, or contributing to 402-mcp.

## What is 402-mcp?

402-mcp is an MCP (Model Context Protocol) server that gives AI agents economic agency: the ability to discover, pay for, and consume L402 and other Lightning or ecash payment-gated APIs without human registration, API keys, or middlemen.

It implements the client side of the L402 protocol: when an API responds with HTTP 402 (Payment Required), 402-mcp automatically parses the challenge, pays the Lightning invoice, stores the credential, and retries the request — all within a single tool call from the agent's perspective.

## Architecture Overview

```
┌─────────────────────────────────────────────────────────────┐
│  AI Agent (Claude, Cursor, GPT, custom)                     │
│  Communicates via MCP protocol (stdio or HTTP transport)     │
└──────────────────────┬──────────────────────────────────────┘
                       │ MCP tool calls
┌──────────────────────▼──────────────────────────────────────┐
│  402-mcp MCP Server                                          │
│                                                              │
│  ┌──────────┐  ┌──────────┐  ┌─────────────┐               │
│  │ Tools    │  │ Wallets  │  │ Stores      │               │
│  │          │  │          │  │             │               │
│  │ config   │  │ NWC      │  │ Credentials │               │
│  │ discover │  │ Cashu    │  │ (AES-256)   │               │
│  │ fetch    │  │ Human    │  │             │               │
│  │ pay      │  │          │  │ Cashu       │               │
│  │ creds    │  │          │  │ Tokens      │               │
│  │ balance  │  │          │  │             │               │
│  │ search   │  │          │  │             │               │
│  │ buy_cred │  │          │  │             │               │
│  │ redeem   │  │          │  │             │               │
│  └──────────┘  └──────────┘  └─────────────┘               │
│                                                              │
│  ┌──────────────┐  ┌──────────────┐  ┌───────────────┐     │
│  │ L402 Protocol│  │ Resilient    │  │ Spend         │     │
│  │ parse, detect│  │ Fetch        │  │ Tracker       │     │
│  │ cache, bolt11│  │ SSRF, retry  │  │ atomic caps   │     │
│  └──────────────┘  └──────────────┘  └───────────────┘     │
└──────────────────────┬──────────────────────────────────────┘
                       │ HTTP + L402 / Nostr
         ┌─────────────┼─────────────┐
         ▼             ▼             ▼
    ┌─────────┐  ┌──────────┐  ┌──────────┐
    │toll-booth│  │ Aperture │  │  Nostr   │
    │ server  │  │  server  │  │  Relays  │
    └─────────┘  └──────────┘  └──────────┘
```

## Source Structure

```
src/
  index.ts                 # Entry point: config, wiring, transport setup
  config.ts                # Environment variable parsing with validation
  spend-tracker.ts         # Atomic rolling-window spend limiter
  tools/
    config.ts              # l402-config — introspect capabilities
    discover.ts            # l402-discover — probe pricing without paying
    fetch.ts               # l402-fetch — full L402 payment flow
    pay.ts                 # l402-pay — explicit invoice payment
    credentials.ts         # l402-credentials — list stored credentials
    balance.ts             # l402-balance — check cached credit balance
    search.ts              # l402-search — discover services on Nostr
    buy-credits.ts         # l402-buy-credits — toll-booth credit tiers
    redeem-cashu.ts        # l402-redeem-cashu — direct ecash redemption
    nostr-subscribe.ts     # Nostr relay subscription for search
    safe-error.ts          # Error sanitisation (strips internal details)
    safe-headers.ts        # Header sanitisation (strips auth tokens)
  wallet/
    types.ts               # WalletProvider interface
    resolve.ts             # Priority-ordered wallet resolution
    nwc.ts                 # @forgesworn/nwc-kit adapter + farrier proof check
    cashu.ts               # Cashu ecash melt implementation
    human.ts               # Human-in-the-loop (QR code + polling)
  l402/
    parse.ts               # Parse WWW-Authenticate L402 challenges
    detect.ts              # Detect toll-booth vs generic L402 servers
    bolt11.ts              # Decode BOLT11 invoices (amount extraction)
    challenge-cache.ts     # Cache parsed challenges for l402-pay
  x402/
    parse.ts               # Experimental custom x402 format: X-Payment-Required: x402 header, EVM challenge body
    payment.ts             # Format payment request with EIP-681 deeplink for human payment
  xcashu/
    parse.ts               # Detect X-Cashu header; parse NUT-18 payment request
    payment.ts             # Autonomous ecash payment: coin-select, send exact token amount
  xlnurlcash/
    parse.ts               # Detect X-LNURLcash header; parse the lnurlcash payment request
                           # (v/id/amount-as-string/currency/methodDetails.mints, per the
                           #  conformance vectors; the older short form is still read)
    payment.ts             # Autonomous bearer-note payment: pick or split a note to the exact price
  ietf-payment/
    parse.ts               # Detect/parse WWW-Authenticate: Payment challenges (draft-ryan-httpauth-payment-01)
    credential.ts          # Build Authorization: Payment base64url credential after Lightning payment
  store/
    credentials.ts         # Encrypted credential persistence (JSON)
    cashu-tokens.ts        # Cashu token store (add/remove/balance)
    encryption.ts          # AES-256-GCM encryption (keychain or file key)
  fetch/
    transport.ts           # URL classification (onion/hns/https/http) and selection
    hns-resolve.ts         # Handshake name resolution via DNS-over-HTTPS (RFC 8484)
    resilient-fetch.ts     # Timeout, retry, response size limits, transport fallback
    ssrf-guard.ts          # Block requests to private/internal IPs, HNS fallback
    errors.ts              # Fetch error types (including TransportUnavailableError)
tests/                     # Mirrors src/ structure
  e2e/                     # Integration tests against in-process toll-booth
```

## Complete Tool Reference

### l402-config

**Purpose:** Introspect the agent's payment capabilities and current configuration.

**Parameters:** None.

**Returns:**
- `nwcConfigured` (boolean) — whether an NWC wallet URI is set
- `cashuConfigured` (boolean) — whether Cashu tokens are loaded with balance > 0
- `cashuBalanceSats` (number) — total Cashu token balance in satoshis
- `maxAutoPaySats` (number) — per-payment safety cap
- `credentialCount` (number) — number of stored L402 credentials

**When to use:** Call first in any session to understand what the agent can do autonomously.

---

### l402-discover

**Purpose:** Probe an endpoint to discover pricing without paying.

**Parameters:**
- `url` (string, required) — the endpoint to probe
- `method` (string, optional) — HTTP method (default: GET)
- `headers` (object, optional) — additional headers

**Returns:**
- `amountSats` (number) — price in satoshis
- `server` (string) — detected server type ("toll-booth" or null)
- `hasTiers` (boolean) — whether credit tiers are available
- `challenge` — cached for subsequent `l402-pay`

**When to use:** Before committing to a purchase, so the agent can reason about value.

---

### l402-fetch

**Purpose:** Make an HTTP request with automatic L402 payment handling.

**Parameters:**
- `url` (string, required) — the endpoint to fetch
- `urls` (string[], optional): all transport URLs from `l402-search`, tried in preference order
- `pubkey` (string, optional): service pubkey from `l402-search`, used to share a credential across the service's transports
- `method` (string, optional) — HTTP method (default: GET)
- `headers` (object, optional) — additional headers
- `body` (string, optional) — request body
- `autoPay` (boolean, optional, default false): pay a challenge within the limits
- `maxCostSats` (number, optional): most this call may pay; lowers `MAX_AUTO_PAY_SATS`, never raises it
- `txHash` (string, optional): transaction hash for the experimental x402 format

**Behaviour:**
1. Uses a stored credential only if it is bound to every origin the request may reach
2. Makes the HTTP request (with credential if available)
3. If 402 received and `autoPay` is set: refuses if an earlier payment to the service is unresolved, then checks the price against `MAX_AUTO_PAY_SATS`, `maxCostSats` and the rolling spend caps
4. If within the limits: pays, stores the credential bound to the request's origins, retries
5. Otherwise: returns the price and `paymentHash` unpaid, and caches the challenge for `l402-pay`
6. A payment whose outcome is unknown is reported as an error and recorded for `l402-reconcile`; it never falls through to another rail
7. Returns response body (in UNTRUSTED CONTENT markers), status, headers, and remaining credit balance

**When to use:** Primary tool for consuming L402 APIs. Handles the entire flow.

---

### l402-pay

**Purpose:** Pay a challenge that `l402-fetch` or `l402-discover` returned.

**Parameters:**
- `paymentHash` (string): payment hash of the cached challenge
- `invoice`, `macaroon` (string, optional): only for an invoice that did not come from a challenge; the human must approve it through MCP elicitation, or it is refused
- `method` (string, optional): `nwc`, `cashu`, `lnurlcash` or `human`

**Returns:**
- `paid` (boolean) and `paymentState` (`paid`, `failed`, `unknown` or `blocked`)
- `credentialsStored` (boolean)
- `method` (string) — which wallet paid

For human wallets with a toll-booth payment page, polls for settlement for up to 120 seconds.

---

### l402-reconcile

**Purpose:** Resolve payments whose outcome is unknown.

**Parameters:**
- `paymentHash` (string, optional): omit to list unresolved payments
- `preimage` (string, optional): settles the payment if it hashes to `paymentHash`
- `abandon` (boolean, optional): stop tracking it; requires the human's approval through MCP elicitation

**Behaviour:** Asks the wallet that made the payment (NWC `lookup_invoice`, the Cashu mint for a reserved melt, or the LNURLcash verify URL). Settled with a matching preimage clears it and stores the L402 credential; definitely failed clears it and releases reserved Cashu proofs. While any is unresolved for a service, auto-pay to it is paused.

---

### l402-fetch-preview

**Purpose:** Show what an endpoint costs without paying. Its dependencies include no wallet or spend tracker, so it cannot spend. Widget hosts render the result as a payment confirmation dialog.

---

### Widget tools

`l402-service-directory` (searchable service directory) and `l402-wallet-dashboard` (wallet status, limits, credentials) are MCP Apps widgets for hosts that support them.

---

### l402-credentials

**Purpose:** List all stored L402 credentials.

**Parameters:** None.

**Returns:** Array of credentials with:
- `origin` — server origin
- `creditBalance` — cached balance (if known)
- `server` — detected server type
- `storedAt` / `lastUsed` — timestamps

**When to use:** Auditing spend, checking which servers have been paid.

---

### l402-balance

**Purpose:** Check cached credit balance for a server.

**Parameters:**
- `url` (string, required) — server URL to check

**Returns:**
- `balance` (number | null) — credits remaining, or null if unknown

**When to use:** Before calling `l402-fetch` to confirm sufficient balance.

---

### l402-search

**Purpose:** Discover L402 services on Nostr relays.

**Parameters:**
- `query` (string, required) — search text to match against service names, descriptions, capabilities
- `relays` (string[], optional) — Nostr relay URLs (defaults to damus, primal, nos.lol)
- `topics` (string[], optional) — filter by topic tags (e.g. ["ai", "data"])
- `paymentMethod` (string, optional) — filter by payment method (e.g. "bitcoin-lightning-bolt11")
- `maxResults` (number, optional) — max results (default 20)
- `timeout` (number, optional) — relay timeout in ms (default 5000)

**Returns:** Array of services with:
- `name`, `urls` (array), `pubkey`, `about` — service metadata
- `pricing` — array of `{ capability, amount, unit }`
- `paymentMethods` — supported payment methods
- `topics` — topic tags
- `capabilities` — array of `{ name, description }`

Names, descriptions and capability text are wrapped in UNTRUSTED CONTENT markers. Topic and payment-method filters are sent to the relays; the query text is matched locally.

**When to use:** When the agent needs a paid API but doesn't know the URL. Searches the decentralised Nostr network for kind 31402 service announcements. Services may advertise multiple transport URLs (clearnet, .onion, Handshake); pass the `urls` array and `pubkey` to `l402-fetch` for automatic transport selection and fallback.

---

### l402-buy-credits (toll-booth only)

**Purpose:** Browse and purchase volume discount credit tiers.

**Parameters:**
- `url` (string, required) — toll-booth server URL
- `amountSats` (number, optional) — specific tier to purchase
- `method` (string, optional): `nwc`, `cashu` or `human`

**Behaviour:** Fetches available tiers, purchases the specified tier, stores the credential with the credit balance.

**When to use:** When multiple requests are needed — buying a tier upfront is cheaper than paying per-request.

---

### l402-redeem-cashu (toll-booth only)

**Purpose:** Redeem Cashu ecash tokens directly with the server.

**Parameters:**
- `url` (string, required) — toll-booth server URL
- `token` (string, required) — Cashu token to redeem

**Behaviour:** Sends the raw Cashu token to the toll-booth's redemption endpoint. The server credits the balance immediately without a Lightning round-trip.

**When to use:** When both agent and server support Cashu — faster and cheaper than melting tokens to pay an invoice.

## L402 Protocol Flow

The L402 protocol is an extension of HTTP 402 (Payment Required):

1. **Client** sends a request to a protected endpoint
2. **Server** responds with `402 Payment Required` and headers:
   - `WWW-Authenticate: L402 macaroon="<base64>", invoice="<bolt11>"`
3. **Client** decodes the BOLT11 invoice, pays it via Lightning, receives a preimage
4. **Client** retries the request with:
   - `Authorization: L402 <base64-macaroon>:<hex-preimage>`
5. **Server** validates the macaroon + preimage and returns the data

402-mcp handles steps 2–5 automatically within `l402-fetch`.

## Supported 402 Protocol Variants

`l402-fetch` detects and handles four HTTP 402 challenge variants, plus an experimental x402 format. Detection order in source: LNURLcash, x402, IETF Payment, L402, with xCashu tried immediately after LNURLcash.

| Protocol | Detection | Credential sent | Autonomous? |
|----------|-----------|----------------|-------------|
| **L402** | `WWW-Authenticate: L402 macaroon=…, invoice=…` | `Authorization: L402 <macaroon>:<preimage>` | Yes (NWC or Cashu) |
| **IETF Payment** (`draft-ryan-httpauth-payment-01`) | `WWW-Authenticate: Payment id=… method=lightning …` | `Authorization: Payment <base64url-json>` | Yes (NWC or Cashu) |
| **LNURLcash** (LUD-25) | `X-LNURLcash: lnurlcashreq1<base64url>` | `X-LNURLcash: <bearer note URL>` | Yes (note store only) |
| **xCashu** (NUT-18) | `X-Cashu: creqA<base64url>` | `X-Cashu: <cashuB-token>` | Yes (Cashu wallet only) |
| **x402** (experimental, custom format) | `X-Payment-Required: x402` + JSON body | `X-Payment: <tx hash>` after the human pays | No: payment details shown to the human. Not the x402 specification, whose servers send a base64 `PAYMENT-REQUIRED` header |

LNURLcash is tried before the others because a bearer note is already money in hand: the note URL goes straight into the retry header and the server settles it by rotating the note. A note worth exactly the price is used as it is; otherwise one is split at the mint and the change stays in the store. The chosen note is held pending until the outcome is known, so nothing else can spend it in the meantime. xCashu uses coin selection via `wallet.send(amount, proofs, { includeFees: true })` and restores change proofs to the token store. IETF Payment decodes the base64url charge request to extract the BOLT11 invoice, then pays it through the normal wallet stack and wraps the preimage in a JSON credential.

## Wallet Priority and Resolution

Wallets are tried in order: NWC → Cashu → LNURLcash → Human. The first wallet that succeeds is used.

| Wallet | Autonomy | Requirements | Speed |
|--------|----------|-------------|-------|
| NWC | Fully autonomous | `NWC_URI_FILE` pointing to a private 0600 bearer file | ~2-5 seconds |
| Cashu | Fully autonomous | `CASHU_TOKENS` file with ecash tokens | ~3-8 seconds |
| LNURLcash | Fully autonomous | `LNURLCASH_NOTES` file with LUD-25 bearer notes | ~2-6 seconds |
| Human | Requires human | None — always available as fallback | Minutes |

The agent can force a specific method on `l402-pay` and `l402-buy-credits` with the `method` parameter.

## Safety and Spend Limits

### Per-payment cap

`MAX_AUTO_PAY_SATS` (default: 1000) caps any single payment. A dearer challenge is not paid from any configured wallet, and the human wallet does not take over: the price and invoice go back to the agent, and whether it asks the human is up to the agent. `maxCostSats` on `l402-fetch` lowers the cap for one call. `0` disables auto-pay.

### Rolling spend cap

`MAX_SPEND_PER_MINUTE_SATS` (default: 10000) caps total spend in any 60-second window, and `MAX_SPEND_PER_DAY_SATS` (default: 5000) in any 24 hours; the daily window is kept in `~/.402-mcp/spend-ledger.json` so a restart does not reset it. `0` in either blocks auto-pay. The budget set on the NWC connection is the real hard limit. These prevent:
- Rapid successive small payments exceeding a total budget
- Runaway loops where the agent keeps buying credits
- Both caps use atomic `tryRecord()` to prevent TOCTOU race conditions

### SSRF Protection

All outbound HTTP requests pass through an SSRF guard:
- Blocks requests to private IP ranges (10.x, 172.16-31.x, 192.168.x, 127.x, ::1)
- Blocks requests to link-local, loopback, and metadata service IPs
- `SSRF_ALLOW_PRIVATE=true` disables this for local development only

### Credential Security

- Credentials encrypted with AES-256-GCM
- Encryption key stored in the OS keychain when available (macOS through the `security` command-line tool; elsewhere through the optional `keytar` module)
- Falls back to file-based key at `~/.402-mcp/encryption.key` with a console warning
- NWC capability and response events authenticated with NIP-44 v2 and signatures
- Returned NWC preimages verified against the challenged BOLT11 payment hash
- Preimages validated as hex before storage to prevent header injection
- Credential store path must be within the home directory (path traversal protection)

## Transport Modes

### stdio (default)

Standard MCP transport — used by Claude Desktop, Cursor, and most MCP clients. The server reads JSON-RPC from stdin and writes to stdout.

```bash
npx 402-mcp
```

### HTTP

StreamableHTTP transport, stateless. Prefer stdio wherever the client can start a local process: the HTTP transport can spend from the wallet.

```bash
openssl rand -hex 32 > ~/.402-mcp/http.token && chmod 600 ~/.402-mcp/http.token
TRANSPORT=http HTTP_AUTH_TOKEN_FILE=~/.402-mcp/http.token PORT=3402 npx 402-mcp
```

HTTP transport includes:
- Bearer token required on every request (`HTTP_AUTH_TOKEN_FILE`, a private 0600 file); the server refuses to start in http mode without one
- DNS rebinding protection: only requests whose `Host` header is a loopback name with the port, the bound address, or listed in `HTTP_ALLOWED_HOSTS` are answered
- Rate limiting: 100 requests per 60 seconds per IP
- Security headers: CSP, X-Frame-Options, X-Content-Type-Options, Cache-Control
- Loopback-only binding by default (`BIND_ADDRESS=127.0.0.1`)
- Health endpoint at `GET /health`
- Graceful shutdown on SIGTERM/SIGINT

## Configuration Reference

| Variable | Default | Description |
|----------|---------|-------------|
| `NWC_URI_FILE` | — | Path to a private 0600 file containing the NWC bearer URI. Raw NWC_URI is refused. |
| `CASHU_TOKENS` | — | Path to Cashu token store file. Must be within home directory. |
| `MAX_AUTO_PAY_SATS` | 1000 | Per-payment safety cap (satoshis). 0 disables autonomous payments. |
| `MAX_SPEND_PER_MINUTE_SATS` | 10000 | Rolling 60-second spend cap (satoshis). 0 blocks auto-pay. |
| `MAX_SPEND_PER_DAY_SATS` | 5000 | Rolling 24-hour spend cap (satoshis), persisted across restarts. 0 blocks auto-pay. |
| `CREDENTIAL_STORE` | `~/.402-mcp/credentials.json` | Path to encrypted credential store. Must be within home directory. |
| `TRANSPORT` | `stdio` | Transport mode: `stdio` or `http`. |
| `PORT` | 3402 | HTTP server port (when `TRANSPORT=http`). |
| `BIND_ADDRESS` | `127.0.0.1` | HTTP bind address. Warning emitted for non-loopback addresses. |
| `HTTP_AUTH_TOKEN_FILE` | - | Private 0600 file holding the bearer token (32+ characters). Required when `TRANSPORT=http`. Raw HTTP_AUTH_TOKEN is refused. |
| `HTTP_ALLOWED_HOSTS` | - | Extra `Host` header values to accept (comma-separated), e.g. a reverse proxy's name. |
| `CORS_ORIGIN` | disabled | CORS origin for HTTP transport. |
| `FETCH_TIMEOUT_MS` | 30000 | HTTP request timeout in milliseconds. |
| `FETCH_MAX_RETRIES` | 2 | Maximum retry attempts for failed requests. Money-mutating POSTs use 0. |
| `FETCH_MAX_RESPONSE_BYTES` | 10485760 | Maximum response body size (10 MB). |
| `HUMAN_PAY_TIMEOUT_S` | 600 | Timeout for human payment (seconds). |
| `HUMAN_PAY_POLL_S` | 3 | Initial polling interval for human payment (seconds). |
| `SSRF_ALLOW_PRIVATE` | false | Allow requests to private IP ranges. Local development only. |
| `NODE_TLS_REJECT_UNAUTHORIZED` | — | Warning emitted if set to "0" (disables TLS validation). |
| `TRANSPORT_PREFERENCE` | `onion,hns,https,http` | Preferred transport order for multi-URL services. |
| `TOR_PROXY` | — | SOCKS5 proxy for .onion requests only (e.g. `socks5h://127.0.0.1:9050`). |
| `SOCKS_PROXY` | — | SOCKS5 proxy for every paid-API request, names resolved by the proxy. Set this or `TOR_PROXY`, not both. Wallet and relay traffic is not proxied. |
| `HNS_GATEWAY_URL` | `https://query.hdns.io/` | DNS-over-HTTPS resolver for Handshake (HNS) names. Any host name ordinary DNS cannot find is sent here. |

## Multi-Transport Endpoints

Services discovered via `l402-search` may advertise multiple transport URLs for censorship resistance. If a DNS seizure, registrar suspension, or CA revocation takes down one endpoint, the agent automatically falls back to an alternative.

### Transport Types

| Transport | Example | Detection |
|-----------|---------|-----------|
| Onion | `http://abc123...onion/api` | Hostname ends in `.onion` |
| Handshake | `https://api.myservice/api` | TLD not in ICANN root zone (heuristic) |
| HTTPS | `https://api.example.com/api` | Standard TLD + `https://` |
| HTTP | `http://api.example.com/api` | Standard TLD + `http://` |

### Selection and Fallback

1. **Filter** — .onion URLs are removed when no Tor proxy is configured
2. **Sort** — URLs are ordered by `TRANSPORT_PREFERENCE` (default: onion > hns > https > http)
3. **Try** — each URL is tried in order; connection-level failures (DNS, ECONNREFUSED, timeout, TLS failure) trigger fallback to the next URL
4. **Stop** — any HTTP response (including 402, 500) is returned immediately without fallback

### HNS Resolution

Handshake names are resolved via DNS-over-HTTPS (RFC 8484 wire format) through the configured gateway. When standard DNS returns NXDOMAIN, the SSRF guard tries HNS resolution as a fallback. Resolved IPs are validated against blocked ranges before connecting.

### Credential Portability

Credentials are keyed by service **pubkey** (from the kind 31402 event) and bound to the transport origins they were bought through. A macaroon obtained via a Tor hidden service works over the service's clearnet URL, and vice versa, when both were in the `urls` list at purchase. A credential is never sent to an origin it is not bound to, whatever pubkey the request names.

## Ecosystem

| Project | Role | URL |
|---------|------|-----|
| **402-mcp** | MCP client — AI agents discover, pay, and consume L402 APIs | https://github.com/forgesworn/402-mcp |
| **toll-booth** | L402 payment middleware for any Node.js server | https://github.com/forgesworn/toll-booth |
| **satgate** | Pay-per-token AI inference proxy (built on toll-booth) | https://github.com/forgesworn/satgate |
| **402-announce** | Publish L402 services on Nostr for decentralised discovery | https://github.com/forgesworn/402-announce |

## Integration Patterns

### Agent discovers and consumes a paid API

```
1. l402-config()                    → Understand capabilities
2. l402-search("routing")           → Find services on Nostr
3. l402-discover(service_url)       → Check pricing
4. l402-fetch(service_url, ...)     → Pay and consume
```

### Agent buys credits for repeated access

```
1. l402-discover(url)               → Detect toll-booth, check tiers
2. l402-buy-credits(url, 500)       → Buy 500-sat tier (555 credits)
3. l402-fetch(url, ...) × N         → Use credits (no payment per-request)
4. l402-balance(url)                → Check remaining credits
```

### Agent redeems Cashu tokens directly

```
1. l402-discover(url)               → Confirm toll-booth server
2. l402-redeem-cashu(url, token)    → Direct ecash redemption
3. l402-fetch(url, ...)             → Use credits from redemption
```

### Agent discovers service with transport fallback

```
1. l402-search("weather data")       → Service found with 3 URLs (clearnet, .onion, HNS)
2. l402-fetch(urls, pubkey: ...)     → Tries .onion first (if Tor configured)
                                       → .onion down? Falls back to HNS
                                       → HNS unreachable? Falls back to clearnet
3. l402-balance(url)                 → Credential works across all transports (keyed by pubkey)
```

### Agent with no wallet (human-in-the-loop)

```
1. l402-fetch(url, autoPay)        → 402 received; payment page URL or QR shown
2. Human pays via wallet app
3. l402-pay(paymentHash)            → polls the toll-booth payment page, stores the credential
4. l402-fetch(url)                  → data returned

Settlement can only be confirmed where the server reports it (toll-booth).
```
