Skip to main content

@faremeter/middleware

@faremeter/middleware protects HTTP endpoints with Coinbase’s x402 handshake. It forwards desired payment requirements to a facilitator, inspects X-PAYMENT headers from clients, and retries settlement before letting the request proceed.
Source: GitHub › packages/middleware
The package ships adapters for Express and Hono plus shared helpers you can reuse in other frameworks.

Quick start (Express)

import express from "express";
import { createMiddleware } from "@faremeter/middleware/express";

const paywalled = await createMiddleware({
  facilitatorURL: "https://facilitator.example.com",
  accepts: [
    {
      scheme: "exact",
      network: "solana-mainnet-beta",
      maxAmountRequired: "1.5",
      description: "Solana USDC payment",
    },
  ],
});

const app = express();
app.use("/premium", paywalled, premiumRouter);
When the client reaches /premium, the middleware:
  1. Calls the facilitator’s /accepts endpoint to produce canonical requirements.
  2. Returns a 402 Payment Required response (with the facilitator payload) if the incoming request lacks a valid X-PAYMENT header.
  3. When a header is present, forwards it to /settle. If settlement succeeds, the request pipeline continues; otherwise another 402 is returned.

API surface

Express adapter

createMiddleware({ facilitatorURL, accepts, cacheConfig? })

Returns an async Express middleware.
  • facilitatorURL: base URL of your facilitator service (mounted routes from @faremeter/facilitator).
  • accepts: array describing the resources you’re willing to accept. Each entry can be a single relaxed requirement object or an array (to represent fallbacks). resource defaults to the incoming request URL if not provided.
  • cacheConfig: optional cache tuning (see below).

Hono adapter

createMiddleware({ facilitatorURL, accepts, cacheConfig? })

Same inputs as the Express variant, returning a MiddlewareHandler. The Hono adapter uses the request URL as the resource string.

Common utilities

handleMiddlewareRequest(args)

Framework-agnostic core used by both adapters. Supply:
  • resource: unique identifier for the paywalled resource (usually a URL).
  • getHeader(key): function that returns header values from the incoming request.
  • sendJSONResponse(status, body): function that sends a response back to the client.
  • getPaymentRequiredResponse: async function returning the facilitator’s JSON (often wrapped with the cache below).
  • Plus the shared facilitatorURL and accepts.
It returns the value of sendJSONResponse when a 402 should be sent; otherwise it resolves to undefined, signaling the request can proceed.

createPaymentRequiredResponseCache(opts?)

Wrap the facilitator calls with an LRU cache to avoid hammering /accepts on every request.
  • opts.capacity (default 256): maximum number of requirement combinations to store.
  • opts.maxAge (default 30_000 ms): how long to keep entries.
  • opts.now: override timestamp source for testing.
  • opts.disable: when true, returns a pass-through implementation that logs a warning.
The cache stores keyed arrays of relaxed requirement inputs; nested arrays in accepts are flattened before lookup.

findMatchingPaymentRequirements(accepts, payload)

Given the facilitator’s accept list and an x402 payment payload, returns the most specific matching requirement (prefers matches on asset, network, and scheme). Emits warnings if multiple candidates match.

getPaymentRequiredResponse({ facilitatorURL, accepts, resource })

Performs the raw network call to /accepts, validating the JSON using @faremeter/types/x402.

AgedLRUCache

Lightweight LRU implementation underpinning the cache helper. Useful if you need to memoize related calls.

Usage patterns

  • Multiple products: Provide an array of arrays in accepts to describe alternative requirement sets. Example: accepts: [[solanaUsdcRequirement, evmUsdcRequirement]].
  • Resource-specific metadata: Leave resource undefined in the accepts entries to let the middleware fill it with the current request URL. You can override per-route if needed.
  • Observability: The module uses ["faremeter","middleware"] logger namespace from @logtape/logtape. Hook into it to track settlement success or diagnose mismatched payloads.

Error handling

  • Invalid facilitator responses throw an exception (HTTP 500). Instrument upstream logging so you can detect schema regressions quickly.
  • When settlement fails (e.g., wrong amount or expired signature), the middleware replays the facilitator payload to the client, prompting another payment.
  • @faremeter/facilitator: the server counterpart that aggregates handlers.
  • @faremeter/fetch: client-side tool that interprets the 402 responses produced by this middleware.
  • @faremeter/payment-*: handler factories you can pass to the facilitator and ultimately surface through this middleware.