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

@furlpay/gateway

v0.1.1

Published

Self-hostable x402 monetization gateway — paywall any API, MCP tool, dataset or model endpoint with stablecoin pay-per-call. Framework-agnostic, zero runtime dependencies.

Readme

@furlpay/gateway

npm version license: MIT node >= 18 zero dependencies

TypeScript Node.js Express USDC x402 MCP

Paywall any API, MCP tool, dataset, or model endpoint with stablecoin pay-per-call.

An x402 monetization gateway you host yourself. Point it at an endpoint, set a price, and AI agents pay per request in USDC — no account, no API key, no subscription, no invoice.

Speaks x402 v1 and v2, negotiated per request. Framework-agnostic. Zero runtime dependencies. MIT.

npm install @furlpay/gateway

Why this exists

Cloudflare and AWS both shipped x402 monetization at the edge in 2026, and both route settlement through Coinbase's facilitator. That's fine if you're happy being a tenant. This package is for everyone who wants the paywall to be theirs: self-hosted, portable across clouds, with a facilitator you choose.

v1 and v2, at the same time

x402 v2 (Dec 2025) is not a header rename. The shapes changed:

| | v1 | v2 | | --- | --- | --- | | request header | X-PAYMENT | PAYMENT-SIGNATURE | | response header | X-PAYMENT-RESPONSE | PAYMENT-RESPONSE | | 402 payload | in the body | in the PAYMENT-REQUIRED header | | price field | maxAmountRequired | amount | | resource metadata | per-requirement | hoisted to ResourceInfo | | client echo | extra | full accepted requirements |

A 402 from this gateway advertises both at once — v2 in the PAYMENT-REQUIRED header, v1 in the body. A v2 agent reads the header, a v1 agent reads the body, and neither needs to know the other exists. (v2 moving payment data out of the body is exactly what makes this possible.) The gateway then answers in whatever dialect the client spoke, and calls the facilitator in that dialect too.

You don't configure any of this. It just works for both.

Quickstart

1. Any fetch-based framework (Next.js, Hono, Workers, Bun, Deno)

// app/api/weather/route.ts
import { paywall } from "@furlpay/gateway";

const pay = paywall({
  price: 0.01,                            // USD per call
  payTo: process.env.MERCHANT_WALLET!,    // your wallet
  quoteSecret: process.env.QUOTE_SECRET!, // see "Configuration"
  network: "base",
});

export const GET = pay(async (req, ctx, receipt) => {
  // Only runs after payment has settled on-chain.
  return Response.json({ tempC: 17, paidBy: receipt.payer });
});

2. Express

import express from "express";
import { expressPaywall, captureRawBody } from "@furlpay/gateway/express";

const app = express();
app.use(express.json({ verify: captureRawBody })); // needed for body binding

app.get("/api/weather", expressPaywall({ price: 0.01, payTo, quoteSecret }), (req, res) => {
  res.json({ tempC: 17, paidBy: req.payment.payer });
});

3. Reverse proxy — monetize something you can't modify

npx @furlpay/gateway secret          # → generates a quote secret
export QUOTE_SECRET=<that value>

npx @furlpay/gateway expose http://localhost:8000 \
  --price 0.01 \
  --pay-to 0xYourWallet \
  --network base \
  --free /health,/openapi.json

Your origin stays untouched and never learns it's being monetized. The origin must not be publicly reachable, or agents will simply route around the paywall.


How a payment actually works

agent                          gateway                      facilitator      chain
  │  GET /weather                 │                              │             │
  ├──────────────────────────────►│                              │             │
  │  402 + PaymentRequirements    │                              │             │
  │◄──────────────────────────────┤                              │             │
  │                               │                              │             │
  │  signs EIP-3009 authorization │                              │             │
  │  (OFF-CHAIN — no gas, no tx)  │                              │             │
  │                               │                              │             │
  │  GET /weather                 │                              │             │
  │  X-PAYMENT: base64(auth)      │                              │             │
  ├──────────────────────────────►│  verify + settle             │             │
  │                               ├─────────────────────────────►│  submits,   │
  │                               │                              │  pays gas   │
  │                               │                              ├────────────►│
  │                               │  settled @ N confirmations   │             │
  │                               │◄─────────────────────────────┤             │
  │  200 + X-PAYMENT-RESPONSE     │                              │             │
  │◄──────────────────────────────┤                              │             │

The agent never submits a transaction and never holds gas. It signs an authorization; the facilitator submits it and pays the gas. If you read a guide that tells you to "verify the transaction hash the client sends you via RPC" — that guide is not describing x402. It's describing a protocol that forces every agent to hold native gas on every chain it might want to buy from, which is exactly the problem x402 exists to remove.

Configuration

| Option | Default | Notes | | --- | --- | --- | | price | — | USD per call. A function (ctx) => number prices per-route or per-caller. | | payTo | — | Wallet that receives payment. | | quoteSecret | — | HMAC secret binding quotes to resources. No default, by design. | | network | base | arbitrum, base, polygon, solana, + testnets. | | asset | canonical USDC | Override for a non-USDC token. | | facilitator | FurlPay | URL or a FacilitatorClient. Any x402 facilitator works. | | facilitatorVersion | mirrors client | Pin to 1 for a v1-only facilitator; v2 payments get downconverted. | | claimStore | in-memory | Must be shared in multi-instance deploys — see below. | | serviceName / tags / iconUrl | — | v2 discovery metadata. See below. | | bindBody | true | Bind the quote to a hash of the request body. | | settle | true | false verifies but does not collect. Rarely what you want. | | onSettled | — | Fires on confirmed settlement — ledger, webhooks, metering. |

Discovery is in the protocol — don't build an index

v2's ResourceInfo carries description, serviceName, tags and iconUrl, and facilitators crawl those fields. Filling them in is how your resource becomes discoverable to agents. You do not need a proprietary directory, and building one would mean maintaining a walled garden that nobody's crawler visits.

paywall({
  price: 0.01,
  payTo,
  quoteSecret,
  serviceName: "Acme Weather",
  tags: ["weather", "forecast"],
  iconUrl: "https://acme.dev/icon.png",
});

quoteSecret has no default because a default would be a published default, and a published default is a forgeable quote for every deployment that didn't override it. Generate one:

npx @furlpay/gateway secret

Running more than one instance

Replay protection is a single-use claim on the EIP-3009 nonce and the quote ID. That claim must be atomic and shared, or it isn't protection.

The classic bug: a Set in module scope looks like replay protection and passes every local test, because in dev there's one process. In production there are N instances, each with its own empty Set, so the same X-PAYMENT replayed N times clears N different Sets and delivers the resource N times — one payment, N deliveries.

The default MemoryClaimStore is correct on exactly one instance and reports isDurable === false. On more than one, use the built-in Upstash store — it's a single SET NX EX over the REST API, so it adds no dependency:

import { paywall, upstashClaimStore } from "@furlpay/gateway";

const pay = paywall({
  price: 0.01,
  payTo,
  quoteSecret,
  claimStore: upstashClaimStore(), // UPSTASH_REDIS_REST_URL + _TOKEN
});

It throws rather than start without credentials, because silently degrading to in-memory would look durable and not be — the worst possible failure mode for replay protection. Any other backend works via claimStoreFrom(setNx).

Assert it at boot so you find out at deploy time, not during an incident:

if (process.env.NODE_ENV === "production" && !pay.gateway.replayProtectionDurable) {
  throw new Error("multi-instance deploy with in-memory replay protection");
}

What this defends against

Every one of these is covered by a test in test/gateway.test.js.

| Attack | Defence | | --- | --- | | Cross-resource substitution — pay $0.001 for /weather, present it at /gpt-5-inference | Quotes carry an HMAC over (method, resource, amount, quoteId, expiry[, bodyHash]). A payment minted for one resource fails the binding check at any other — even at the same price. | | Body substitution — pay for {"model":"small"}, retry with {"model":"large"} | The quote is bound to a hash of the exact body it was issued for. | | Replay / duplicate settlement — send one X-PAYMENT N times | The nonce and quote ID are burned via an atomic single-use claim, before settlement. Concurrent replays of one payment yield exactly one grant. | | Revert-grant — resource delivered on a settlement that later reorgs | Confirmation depth scales with value: 3 under $1, 6 under $10, 12 above. Shallower than required → no delivery. | | Cache leakage — a CDN caches the paid 200 and serves it free | Cache-Control: no-store + Vary: X-PAYMENT on paid responses and on 402 quotes. In proxy mode the origin's own cache headers are overridden. | | Underpayment — pay 9000 against a 10000 quote | Amounts compare as integers. ("9000" > "10000" lexicographically — a string compare here is a real vulnerability.) | | Quote forgery / tampering | Constant-time HMAC compare. Amount, recipient, network, and expiry are all re-derived server-side; the client's echo is a lookup key, never an assertion. | | Cross-dialect double-spend — pay a quote as v1, re-present the same quote as v2 with a fresh nonce | The quoteId claim is dialect-blind. One quote buys exactly one delivery, in any version. | | Facilitator outage | Fails closed. A facilitator you can't reach costs you a sale, not the resource. | | Settlement error | The nonce stays burned — a failed settle may still land on-chain, so freeing it would reopen replay against a payment that does confirm. Honest retries sign a fresh nonce, so this never blocks a real payer. | | upto over-reporting | v2's upto scheme can settle less than authorized. The receipt reports what was actually taken, not what was authorized. |

For facilitator-side hardening (allowance overdraft, denial-of-settlement, hidden-compute pricing), see @furlpay/x402-guard.

Choosing a facilitator

The facilitator is the only party that touches a chain. The gateway never needs a private key, an RPC endpoint, or a gas balance — which is what makes it safe to self-host.

paywall({ facilitator: "https://furlpay.com/api/x402/facilitator" }) // default
paywall({ facilitator: "https://x402.org/facilitator" })             // or anyone else
paywall({ facilitator: myCustomClient })                             // or your own

License

MIT