Private x402
HTTP 402 payments from a private balance. The server gets paid and can check it; the chain shows a pool transaction, not who paid whom or how much.
- Scheme
- hushd
- Network
- near-testnet
- Header
- X-PAYMENT
How it works
The code is in packages/sdk/src/x402.ts: paymentRequired and verifyX402Payment for a server, x402Fetch for a client.
The 402 answer
A resource that wants a private payment answers 402 Payment Required with an accepts entry of scheme hushd:
{
"x402Version": 1,
"error": "Payment required",
"accepts": [{
"scheme": "hushd",
"network": "near-testnet",
"maxAmountRequired": "10000",
"asset": "usdc.hushd.testnet",
"payTo": "hushd1…",
"resource": "https://example.com/report",
"maxTimeoutSeconds": 120,
"extra": { "pool": "pool.hushd.testnet" }
}]
}maxAmountRequired is in the token's base units (here 0.01 test USDC, which has 6 decimals), and payTo is the server's receive address.
Paying
x402Fetch fetches the URL; on a hushd 402 for this network it pays from the wallet, then fetches again with the X-PAYMENT header. You pass a cap per token, and a price above it is refused, never paid.
import { x402Fetch } from "@hushd/sdk";
const { response, paid } = await x402Fetch(wallet, "https://example.com/report", {}, {
// the most one call may pay, per token, in base units
maxAmount: (token) => (token === "usdc.hushd.testnet" ? 1_000_000n : 0n),
});The header is base64 JSON with the scheme, the network and a payload of four fields: the transaction hash, the account that sent it (the relayer), the commitment of the note made for the payee, and that note's opening, sealed to the payee's key. From an agent, the MCP tool hushd_pay_x402 does all of this inside the agent's spending limits.
Checking a payment
import { paymentRequired, paymentResponse, verifyX402Payment, x402Network, TESTNET } from "@hushd/sdk";
const price = { maxAmountRequired: "10000", asset: "usdc.hushd.testnet" };
const header = req.headers.get("x-payment");
if (!header) return json(paymentRequired(TESTNET, { ...price, payTo: me.address, resource: req.url }), 402);
const paid = await verifyX402Payment(TESTNET, me, header, price, seen);
if (!paid.ok) return json({ error: paid.reason }, 402);
// serve, and say which transaction paid for it
return json(body, 200, { "x-payment-response": paymentResponse({ txHash: paid.txHash, network: x402Network(TESTNET) }) });me is the server's Hushd identity. verifyX402Payment refuses the payment unless every one of these holds:
- the scheme is
hushdand the network is this one, - the commitment has not been used before,
- the opening is sealed to this payee and opens to that exact commitment under its receive key,
- the note is in the asked token, for at least the price,
- the transaction is final and successful, and
- the pool's
transactevent in that transaction created exactly that note.
Replays
seen is your record of commitments already accepted: anything with has and add. Keep it for as long as you accept payments, and on disk if the server restarts, as the reference makers do. Each payment note can then pay for one request only.
What the chain shows
One pool transact sent by the relayer, with two new note commitments, and a separate encrypted board post. Not the payer, not the payee and not the amount. The payee learns the amount and gets a note only it can spend. The payee does not learn the payer's account either: the transaction comes from the relayer.
A live example: maker quotes
The reference makers sell indicative quotes over private x402 at https://api.hushd.net/makers/<maker account>/quote?sell=&amount=&buy=, with the amount in base units. Without a payment it answers 402; with one, a quote that is valid for 30 seconds. See makers.
curl -i "https://api.hushd.net/makers/steady/quote?sell=usdc.hushd.testnet&amount=1000000000&buy=btc.hushd.testnet"
# HTTP/1.1 402 Payment Required