Documentation Index
Fetch the complete documentation index at: https://docs.faremeter.xyz/llms.txt
Use this file to discover all available pages before exploring further.
@faremeter/middleware adds payment walls to your HTTP server. It supports both x402 and MPP protocols. When a request hits a protected route, the middleware checks for a valid payment header. If missing, it returns a payment challenge. If present, it verifies the payment through a handler and allows the request to proceed.
pnpm add @faremeter/middleware
How it works
- A request arrives at a protected endpoint.
- The middleware checks for a payment header (
X-PAYMENT or PAYMENT-SIGNATURE).
- If missing: returns
402 Payment Required with the configured requirements.
- If present: sends the payment to the facilitator for verification and settlement.
- If the facilitator confirms: the request continues to your handler.
- If the facilitator rejects: returns an error response.
Framework support
| Framework | Import path |
|---|
| Express | @faremeter/middleware/express |
| Hono | @faremeter/middleware/hono |
| Custom | @faremeter/middleware/common |
The common module provides the core payment verification logic without framework bindings. Use it to integrate with any HTTP server.
Configuration
The middleware supports two configuration modes: in-process handlers and remote facilitator. Both are configured through CommonMiddlewareArgs. The two modes are mutually exclusive for x402 — pass either x402Handlers/pricing or facilitatorURL/accepts, not both. You can combine remote x402 handlers with in-process MPP handlers using createRemoteX402Handlers.
In-process handlers
Pass payment handlers directly to the middleware. The middleware calls them locally to resolve requirements, verify, and settle payments — no separate facilitator service needed. See the Facilitator guide for how to create handlers.
import { createMiddleware } from "@faremeter/middleware/hono"
const middleware = await createMiddleware({
x402Handlers: [solanaHandler, evmHandler],
mppMethodHandlers: [mppChargeHandler],
pricing: [
{ amount: "10000", asset: "USDC", recipient: "7xKX...", network: "solana:devnet" },
{ amount: "10000", asset: "USDC", recipient: "0x1234...", network: "eip155:84532" },
],
})
| Option | Type | Description |
|---|
x402Handlers | FacilitatorHandler[] | Optional. x402 payment handlers for in-process settlement. |
mppMethodHandlers | MPPMethodHandler[] | Optional. MPP method handlers for in-process settlement. |
pricing | ResourcePricing[] | What to charge. Required when using in-process handlers. |
supportedVersions | SupportedVersionsConfig | Optional. Which x402 protocol versions to serve (v1, v2, or both). Defaults to v1 only. |
ResourcePricing is the resource server’s statement of “I want X amount of Y asset paid to Z recipient on W network.” It says nothing about x402 schemes or protocol extras — those are determined by the handlers. The recipient field maps to payTo in the underlying x402PaymentRequirements that the handler produces.
Remote facilitator
Communicate with a separate facilitator service over HTTP for x402 payments. This is the original configuration model and is still fully supported.
import { createMiddleware } from "@faremeter/middleware/hono"
const middleware = await createMiddleware({
facilitatorURL: "https://facilitator.corbits.dev",
accepts: [
// Payment requirements
],
cacheConfig: {
// Optional LRU cache settings
},
})
| Option | Type | Description |
|---|
facilitatorURL | string | URL of the facilitator server. |
accepts | x402PaymentRequirements[] | Payment methods this endpoint accepts. |
cacheConfig | object | Optional cache configuration for facilitator responses. |
supportedVersions | SupportedVersionsConfig | Optional. Which x402 protocol versions to serve (v1, v2, or both). Defaults to v1 only. See How x402 Works. |
Mixing remote x402 with in-process MPP
Use createRemoteX402Handlers to wrap a remote facilitator as in-process handlers, then combine with MPP handlers under a shared pricing config:
import { createMiddleware } from "@faremeter/middleware/hono"
import { createRemoteX402Handlers } from "@faremeter/middleware"
const x402Handlers = createRemoteX402Handlers({
facilitatorURL: "https://facilitator.corbits.dev",
accepts: [/* x402 payment requirements */],
})
const middleware = await createMiddleware({
x402Handlers,
mppMethodHandlers: [mppChargeHandler],
pricing: [
{ amount: "10000", asset: "USDC", recipient: "7xKX...", network: "solana:devnet" },
],
})
Defining payment requirements
Use @faremeter/info to construct payment requirements:
import { x402Exact } from "@faremeter/info/solana"
const accepts = [
x402Exact({
network: "devnet",
payTo: "7xKXwxRPMo2sUAT5...",
asset: "USDC",
amount: "10000",
}),
]
You can accept multiple payment methods by passing an array. The Solana x402Exact returns an array (one entry per legacy network ID), while the EVM x402Exact returns a single object. The middleware’s accepts field supports nested arrays, so both can be passed directly:
import { x402Exact as solanaX402Exact } from "@faremeter/info/solana"
import { x402Exact } from "@faremeter/info/evm"
const accepts = [
solanaX402Exact({ network: "devnet", payTo: "7xKX...", asset: "USDC", amount: "10000" }),
x402Exact({ network: "base-sepolia", payTo: "0x1234...", asset: "USDC", amount: "10000" }),
]
Caching
The middleware caches facilitator /accepts responses using an LRU cache with TTL. This reduces the number of requests to the facilitator. Cache configuration is optional.
Dynamic pricing
With in-process handlers, dynamic pricing happens naturally. When the middleware calls a handler’s getRequirements method, it passes the resource URL and payment requirements as context. The handler can inspect the resource and return different prices per request. You configure base pricing via ResourcePricing and let the handler adjust.
For the remote facilitator path, or when you need full control over per-request pricing from the middleware layer, use handleMiddlewareRequest.
handleMiddlewareRequest — the lower-level API
createMiddleware (used by the Express and Hono integrations) accepts static configuration at initialization. When you need to compute requirements per-request — for example, varying the price based on a query parameter, header, or request body — use handleMiddlewareRequest from @faremeter/middleware/common.
The tradeoff is that you wire up the framework integration yourself: reading headers, sending JSON responses, and calling your route handler.
import {
handleMiddlewareRequest,
resolveSupportedVersions,
} from "@faremeter/middleware/common"
When to use which
| createMiddleware | handleMiddlewareRequest |
|---|
| Pricing | Fixed per route | Dynamic per request |
| Framework | Express or Hono | Any HTTP server |
| Setup | One-liner middleware | Manual header/response wiring |
| Use case | Most endpoints | Dynamic pricing, custom frameworks |
Signature
handleMiddlewareRequest accepts a single options object:
| Option | Type | Description |
|---|
x402Handlers | FacilitatorHandler[] (optional) | x402 payment handlers. |
mppMethodHandlers | MPPMethodHandler[] (optional) | MPP method handlers. |
pricing | ResourcePricing[] | Required. Payment pricing for this request. |
resource | string | The URL being accessed (typically req.url). |
supportedVersions | Required<SupportedVersionsConfig> | Which x402 protocol versions to support. Use resolveSupportedVersions(). |
getHeader | (key: string) => string | undefined | Read a request header. |
sendJSONResponse | (status, body, headers?) => Response | Send a JSON response with status code and optional headers. |
setResponseHeader | (key, value) => void (optional) | Set a response header. |
getBody | () => Promise<ArrayBuffer | null> (optional) | Read the request body (for MPP digest binding). |
body | (ctx: MiddlewareBodyContext) => Promise<Response | void> | Called when a valid payment is received. |
The body callback
The body function runs when the client sends a valid payment. It receives a context object with a protocolVersion discriminant:
x402 v1 context (protocolVersion: 1):
settle(), verify(), paymentPayload, paymentRequirements
x402 v2 context (protocolVersion: 2):
settle(), verify(), paymentPayload, paymentRequirements
MPP context (protocolVersion: "mpp"):
Use protocolVersion to narrow the context type before accessing protocol-specific fields:
body: async (context) => {
const result = await context.settle()
if (!result.success) {
return result.errorResponse
}
// Proceed to route handler
}
Example: Hono with dynamic pricing
import { Hono, type MiddlewareHandler } from "hono"
import {
handleMiddlewareRequest,
resolveSupportedVersions,
} from "@faremeter/middleware/common"
import { createRemoteX402Handlers } from "@faremeter/middleware"
import { x402Exact } from "@faremeter/info/evm"
const dynamicPricing = (): MiddlewareHandler => {
return async (c, next) => {
const amount = c.req.query("amount") ?? "10000"
const x402Handlers = createRemoteX402Handlers({
facilitatorURL: "https://facilitator.corbits.dev",
accepts: [
x402Exact({
network: "base-sepolia",
asset: "USDC",
amount,
payTo: process.env.MERCHANT_ADDRESS,
}),
],
})
return await handleMiddlewareRequest({
x402Handlers,
pricing: [
{ amount, asset: "USDC", recipient: process.env.MERCHANT_ADDRESS, network: "eip155:84532" },
],
supportedVersions: resolveSupportedVersions(),
resource: c.req.url,
getHeader: (key) => c.req.header(key),
sendJSONResponse: (status, body, headers) => {
if (headers) {
for (const [key, value] of Object.entries(headers)) {
c.header(key, value)
}
}
c.status(status)
return c.json(body)
},
body: async (context) => {
const result = await context.settle()
if (!result.success) return result.errorResponse
await next()
},
})
}
}
See the Dynamic Pricing recipe for a complete multi-chain example with client code.
Framework-specific guides