Documentation. Your keys never move.
The complete reference for building on stealthPAY: the @stealthsend/x402 server SDK, the self-hosted sidecar, the facilitator API, and the viewing-key Console. Settlement is feeless native XST; the private rail is XSS — same SDK, same flow. Everything speaks the open x402 standard (v2). For the conceptual tour, read developers.
Quickstart
Install the SDK, wrap a route with paymentRequired, and price it in atomic XST units (1 XST = 1,000,000 units; the transparent floor is 10000 = 0.01 XST). An unpaid request gets a 402; the facilitator settles; your handler runs with the receipt on req.x402. Zero dependencies, Node ≥ 18, ESM.
Concepts & keys
Four parties, four credentials. Only the wallet holds a key that can move funds. There is no “sign up” on the pay side — just Connect, which generates a key on first run (non-custodial, seed shown once) or signs a challenge if a wallet is present. The server stores nothing.
- Wallet · spending key — builds, grinds, and signs the x402 payment. The spending key never leaves the device. Self-custody, standalone or as a Telegram Mini App.
- Sidecar · capped agent credential — each agent calls its own sidecar with a bearer token. The sidecar holds only a capped HD sub-account (model A: the sub-account’s on-chain balance is its hard ceiling), bounded by your caps, rate, and allowlists.
- Console · viewing key + human login — team members sign in by magic link or OAuth; a read-only viewing key decrypts shielded XSS balances and receipts client-side. Read-only by construction.
- Facilitator · no keys — verifies and settles over x402. Holds no spending key and no funds; it never custodies.
Server SDK
Two pieces, zero dependencies: paymentRequired(options) middleware for Express / Connect / plain node:http, and FacilitatorClient, a typed wrapper over the facilitator surface.
| paymentRequired option | Type | Detail |
|---|---|---|
| facilitator | string (URL) | Facilitator base URL, e.g. http://127.0.0.1:8402. |
| amount | string | number | Atomic units (1 XST = 1e6). "10000" = 0.01 XST. |
| resource | string | Optional. Defaults to the request URL. |
| description | string | Optional. Shown in the challenge. |
| settleMode | 'confirmed' | 'mempool' | 'auto' | Finality to wait for. auto picks by amount. |
| timeoutSeconds | number | Optional. Clamped 10–110; default 90. |
On a request with no PAYMENT-SIGNATURE header it mints a challenge and answers 402 with the standard body. With the header it settles (the facilitator resolves the challenge from the payment, so the server stays stateless), sets PAYMENT-RESPONSE (base64 JSON), exposes the receipt as req.x402, and calls next().
| FacilitatorClient(base) | Calls | Returns |
|---|---|---|
| supported() | GET /supported | { kinds: [{ x402Version, scheme, network }] } |
| challenge(opts) | POST /challenge | PaymentRequirements (fresh payTo + tip anchor) |
| verify(header, reqs?) | POST /verify | { isValid, invalidReason? } — advisory |
| settle(header, reqs?) | POST /settle | { success, network, transaction, extensions, errorReason? } |
Facilitator API
The facilitator verifies and settles. It holds no spend keys and no funds at risk. CORS is open (*); only /grind is authenticated. Protocol is x402 v2.
| Endpoint | Does | Detail |
|---|---|---|
| GET /supported | Scheme / network | { kinds:[{ x402Version:2, scheme:"exact", network }] } |
| POST /challenge | Mint a 402 | Body needs exactly one of amount | fiat. Returns PaymentRequirements with a fresh single-use payTo and a tip anchor. |
| POST /verify | Advisory check | { x402Version:2, paymentHeader, paymentRequirements? } → { isValid, invalidReason? }. Read-only, never broadcasts. |
| POST /settle | Broadcast + confirm | Same body. Re-verifies, broadcasts, waits for finality. Idempotent — extensions.alreadySettled marks a replay. |
| POST /grind ext | Assisted feework | Bearer auth. { transaction, anchorHeight, anchorHash, mcost } → { work }. For thin clients; carries no keys. |
| GET /stats ext | Telemetry | { settledCount, totalXst, recent:[…] } — the live settlement feed. |
| GET /rate ext | Fiat reference | Operator-attested XST/USD TWAP. 404 when fiat quoting is off — stealthPAY never operates rate infra. |
Sidecar
Runs in your infra, bound to 127.0.0.1. Holds only capped agent credentials (model A), enforces caps / rate / allowlists, signs and settles. Every route except /healthz takes the agent’s bearer token (Authorization: Bearer sk_…).
| Endpoint | Body / auth | Returns |
|---|---|---|
| GET /healthz | none | { ok, mode, network, agents } |
| GET /policy | bearer | { agent, capDaily, capTotal, ratePerHour, allow, enabled } |
| GET /balance | bearer | { spentDay, remainingDaily, spentTotal, remainingTotal, pending, rateLastHour } |
| POST /pay | x402 requirement (+ twoPhase) | Policy-check, sign, settle. Returns the built payment + { ok, agent, amountXST, chargeId, pending }. |
| POST /settled | { chargeId } | Capture a reserved charge → { ok, settledXST } (two-phase). |
| POST /cancel | { chargeId } | Release a reserved charge → { ok, released:true }. |
Console API
The Console is the viewing-key dashboard — balances, per-agent spend, the live tx feed, receipts, and policy. Its API is read-only and scoped to an organization. Humans authenticate by magic link or OAuth; the viewing key decrypts shielded XSS data client-side, so the Console can see, never spend.
| Endpoint | Does | Detail |
|---|---|---|
| GET /api/me | Session | The signed-in user + org memberships / roles. |
| GET /api/org/{id}/overview | Summary | Org balances, spend, headline metrics. |
| GET /api/org/{id}/agents | Agents | Per-agent caps, spend, credential status. |
| GET /api/org/{id}/transactions | Activity | The settlement feed for the org. |
| GET /api/org/{id}/receipts | Receipts | Settled-payment receipts for export / reconciliation. |
| auth | Sign in | POST /auth/magic/start + /auth/magic/verify, or /auth/oauth/{provider}/start + /callback; /auth/logout. |
Networks & units
stealthCORE · XST
stealthPRIVATE · XSS
Shielded payments
Same exact scheme, privacy carried by the network string. The flow differs in a few real ways:
- payTo is a bech32 shielded address (xss1… / txss1…), minted fresh per challenge at account'/3/index.
- The payload carries a disclosure, not a transaction — a ShieldedDisclosure (cm, recipientAk, value, rho, rho1, memoHex) instead of a raw signed tx.
- No broadcast, no mempool mode — the payment was already gossiped; settle polls until the note commitment cm is included under the latest checkpoint anchor. “Confirmed” = inclusion.
- The receipt id is the note commitment (cm, 32 hex bytes), not a txid.
- Sub-cent pricing — the shielded pool has no dust floor, and pool settlement accumulates many micro-receipts before a bulk exit to Core, lifting the transparent throughput ceiling.
Payment proofs
A shielded payment is private, so there is no public receipt. The payer can voluntarily disclose one payment with a self-contained PaymentProof, recovered from their own outgoing notes via the Outgoing Viewing Key. The OVK never leaves the wallet; verification needs no keys.
Verification runs three keyless checks: the opening — H(value‖ak‖ρ₀‖ρ₁) = cm; inclusion — root_from_path(cm, position, path) = anchor_root; and anchor — that anchor_root is a canonical XSS anchor. The stealthPAY extension exports the proof from an XSS transaction; verification happens on StealthMonitor via POST /verify-disclosure (or GET /anchor/{root} against your own node).
Addresses
| Kind | Format | Detail |
|---|---|---|
| XST P2PKH | Base58Check | Version byte 0x3e (‘S’), sha256d checksum. The per-challenge transparent payTo. |
| XST stealth (v2) | Base58Check | Version 0x2b: flags ‖ scan_pubkey(33) ‖ spend_pubkey(33) ‖ prefix. DKSAP receiving address. |
| XSS shielded | bech32m | HRP xss (main) / txss (test); 41-byte payload version(0) ‖ ak(8) ‖ enc_pk(32). |
Errors & status
| Status / reason | Where | Meaning |
|---|---|---|
| 402 Payment Required | Gated route | No settled payment — body carries the x402 challenge. |
| 200 OK + req.x402 | Gated route | Settled; your handler ran. Receipt is on req.x402 / PAYMENT-RESPONSE. |
| verify invalidReason | Facilitator | wrong_amount, unknown_challenge, challenge_expired, inputs_spent, script_invalid, feework_insufficient, … |
| 403 policy | Sidecar /pay | Cap (daily/total), rate, or allowlist would be breached. Nothing signed. |
| 409 duplicate | Sidecar /pay | A payment for this Idempotency-Key is already in flight. |
| 401 unauthorized | Sidecar / Console | Missing or unknown bearer token / session. |
Start building.
Gate a route, run your sidecar, point your agents. Your keys never move.