npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

x402-stellar-paywall

v0.3.1

Published

One-line x402 paywall middleware for Stellar, with a facilitator preset and an optional anonymous-credential gate judged on-chain. Express, Hono, and a framework-free core.

Readme

x402-stellar-paywall

One line to charge an AI agent for an HTTP route, on Stellar.

npm install x402-stellar-paywall
import { expressPaywall } from "x402-stellar-paywall/express";

const pay = await expressPaywall({ price: "0.10", payTo: process.env.STELLAR_RECIPIENT });

app.get("/weather", pay, (req, res) =>
  res.json({ temp: 24, settled_by: req.x402.transaction })
);

That is the whole integration. The agent holds no XLM — the facilitator sponsors the network fee — and the handler receives the settlement transaction on req.x402 without knowing anything about the protocol.

Hono is the same shape:

import { honoPaywall } from "x402-stellar-paywall/hono";
const pay = await honoPaywall({ price: "0.10", payTo });
app.get("/premium", pay, (c) => c.json({ ok: true, tx: c.get("x402").transaction }));

The other gate

A paywall answers did they pay. Metered APIs usually have a second question — are they allowed to buy this at all — and the usual answers, an API key or an account, make the caller identifiable across every request they ever make.

Pass a credential option and the route additionally demands a zero-knowledge proof, judged by a Soroban contract:

const pay = await expressPaywall({
  price: "0.10",
  payTo: process.env.STELLAR_RECIPIENT,
  credential: { contract: "CA6QM6DR…", source: "my-key" },
});

app.post("/pq/unlock", pay.unlock);   // proofs are traded for single-use passes
app.get("/premium", pay, handler);

What the server never learns: which credential. What it does learn: that one exists, is valid, and has not already been spent on this challenge. The verdict comes from the contract, which burns a per-challenge nullifier, so single use survives a restart of your process.

The credential is checked before the payment. Charging someone and then refusing them is worse than refusing them, and it is the server's job not to arrange that. It is consumed only once the request succeeds, because the x402 handshake sends the same request twice — once to be told the price, once carrying the payment — and a gate that spent the pass on sight refused its own second half.

Requires the stellar CLI on PATH, and a deployed verifier. The one in the example is riverrun's post-quantum credential relation; any contract with the same spend(proof, publics) -> bool shape works.

The gate that does not learn who

credential: { mode: "crowd" } swaps the identifying credential for an unlinkable one. Instead of the leaf, the relation publishes

C = compress(leaf ‖ blinder)

with a fresh blinder each use, and never the leaf. Two halves compose on that shared C: one proves a valid credential acted under it and burns a nullifier, the other proves C sits under the issuer's published root via a private path.

What the server learns: someone in the issuer's set paid. Not which member. And two payments by one credential share no public value at all, because the blinder is fresh — so they cannot be linked to each other either.

credential: {
  mode: "crowd",
  actContract: "CAEZ25KZ…",        // verifies the acting half and burns
  membershipContract: "CDO2NDPR…",  // verifies membership under a root
  root: process.env.ISSUER_ROOT,
  source: "my-key",
}

The challenge is not the server's to choose. act derives the expected round from the ledger sequence and refuses anything else, so freshness comes from consensus rather than from whoever is asking. One credential therefore acts once per epoch, which is about an hour at Stellar's close time.

Membership is checked before the acting half, because membership is a free read-only call and acting costs a transaction: there is no reason to burn a nullifier for a caller who was never in the set. And the root is compared against the issuer's, because a membership proof is only ever a statement about the tree the prover chose — without that check anyone can build a tree containing their own leaf and prove membership in it.

Proven end to end on testnet in examples/crowd-app: burn d87d75d6aa291b42e37bfdc4d56f8b868bae270d53239bb8dd656aec5f237748, settlement 7b47f7fbd23ad6ed44b667903c66adb634fd721cb49e0f6d0c8606a3172940ec, and the response the server could honestly give: paid for by a member of the set, and the set is all this server knows.

Why this exists

Getting an x402 client and a Stellar facilitator to agree took four failing rounds, each with an error message that pointed somewhere other than the cause. This package encodes all four as behaviour, because a comment does not stop anyone from getting it wrong:

  1. v2 carries the requirements in a payment-required HEADER. The body is read only when x402Version === 1. A server that publishes perfect requirements as JSON alone is invisible to a v2 client, which fails with Invalid payment required response having never looked at them. Every 402 a gated route emits needs the header — including the one that means "you have a session but still owe payment", which is the one everybody forgets.
  2. The signed payload returns in PAYMENT-SIGNATURE, with X-PAYMENT the v1 name. This reads both.
  3. extra.areFeesSponsored must be in the requirements or the Stellar exact scheme refuses to build a payment at all. That flag belongs to the facilitator, so this fetches /supported at startup and copies what it says instead of asserting it on the facilitator's behalf.
  4. Stellar USDC has seven decimals, not six. 0.10 is 1000000 base units. The EVM habit is a 10× underpayment that settles quietly, which is the worst kind of bug: it works.

Setup fails loudly, not at the till

paywall() is awaited because it talks to the facilitator before returning. A missing or wrong-network API key throws at startup with the command that fixes it, rather than letting the server advertise a price it cannot settle:

paywall: facilitator https://channels.openzeppelin.com/x402/testnet supports no
exact scheme on stellar:testnet. no OZ_API_KEY set — mint one with:
curl -s https://channels.openzeppelin.com/testnet/gen

Options

| option | default | notes | |---|---|---| | price | required | human units, e.g. "0.10" | | payTo | required | classic G… account; needs a USDC trustline | | network | stellar:testnet | CAIP-2 id; stellar:pubnet for mainnet | | asset | USDC SAC for the network | the C… contract, not the G… issuer | | facilitatorUrl | OZ Channels for the network | any x402 facilitator | | apiKey | process.env.OZ_API_KEY | required on testnet too | | maxTimeoutSeconds | 60 | bounds the auth entries' ledger validity | | resource | {} | description, mimeType, serviceName for the 402 |

payTo is an account, asset is a contract. The scheme invokes transfer on the SAC; the account is what ends up holding the balance, which is also why it needs a trustline. Mixing them up produces op_no_trust at settlement, long after the mistake.

Verify before settle

The order is not stylistic. settle moves money, so a payload that fails verification must never reach it. A refusal returns the facilitator's own reason rather than a bare 402, and repeats the requirements in the response — a client retrying with a stale or wrong-priced payload needs the current terms to recover on its own.

Framework-free core

Both bindings are thin wrappers over paywall(), which returns primitives you can mount anywhere — FastAPI via a shim, a raw node:http server, a worker:

const gate = await paywall({ price: "0.10", payTo });

gate.challengeHeader(url);   // { "payment-required": "<base64url>" }
gate.paymentRequired(url);   // the same object, for the body and for humans
gate.readPayment(getHeader); // PAYMENT-SIGNATURE, falling back to X-PAYMENT
await gate.settle(encoded);  // { ok, settlement } | { ok:false, status, body }

Proven, not asserted

Extracted from pq402 and then used to gate a different app — examples/second-app, a weather API with no credential logic in it at all — which an agent paid on Stellar testnet:

← 200 unlocked
{"city":"Florianópolis","temp":24,"settled_by":"73d850044aecb628a07b1cc8f9b684fa5…"}

That transaction is successful at ledger 3,966,679, and its fee was paid by GA6THKUY… — the facilitator, not the agent.

The same app, re-run against this package installed from npm rather than from the working tree, settles the same way: 4bfd5a8f75522b20556803ae84857331059d3c74acf92aa27854108b56ec289c.

Tests

npm test    # node --test test/*.mjs

Seventeen of them, all offline. What they pin is the protocol shaping — that the requirements carry the facilitator's own extra rather than a claim made on its behalf, that the challenge decodes to the same document it advertises, that both payment header names are read, that a bad key fails with the command that fixes it, and that a failed verify never reaches settle. That last one has a test because settle moves money and the ordering is the only thing standing between a rejected payload and a real transfer.

MIT.