New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

@suverselabs/x402-server

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@suverselabs/x402-server

x402 server middleware for Node.js (Express + Fastify). Issues 402 Payment Required challenges, verifies + settles via a remote facilitator, exposes the payment metadata to your handler.

latest
Source
npmnpm
Version
0.3.2
Version published
Weekly downloads
0
Maintainers
1
Weekly downloads
 
Created
Source

@suverselabs/x402-server

npm license

Express and Fastify middleware that turns any HTTP route into a paid endpoint, settling stablecoin payments through a remote x402 facilitator (default: https://facilitator.suverse.io).

As of v0.3.0 the middleware auto-discovers network-specific infrastructure data (Solana feePayer, Cosmos grantee address, EVM EIP-712 USDC domain) from the facilitator's /supported endpoint and merges it into every 402 challenge — sellers configure only what they own (payTo, maxAmountRequired, scheme) and never need to know adapter-internal addresses.

client                middleware                 facilitator
  │                       │                            │
  ├── GET /paid ─────────▶│                            │
  │                       ├─ no X-Payment → 402 ─◀─────│
  │◀── 402 + challenge ───┤                            │
  │                       │                            │
  │ (signs USDC payment) │                            │
  │                       │                            │
  ├── GET /paid + X-Payment ▶│                         │
  │                       ├── POST /facilitator/verify ▶│
  │                       │◀────── { isValid: true } ──┤
  │                       ├── POST /facilitator/settle ▶│
  │                       │◀── { success:true, tx:0x.. }┤
  │◀── 200 + your handler ┤                            │

Install

npm install @suverselabs/x402-server
# Peer deps: install whichever you use
npm install express          # for the Express adapter
npm install fastify          # for the Fastify adapter

Express

import express from "express";
import { createExpressMiddleware } from "@suverselabs/x402-server/express";

const app = express();

app.use(
  "/paid",
  createExpressMiddleware({
    apiKey: process.env.SUVERSE_PAY_API_KEY!,
    facilitator: "https://facilitator.suverse.io",
    acceptedPayments: [
      {
        scheme: "exact",
        network: "eip155:8453",
        asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
        payTo: "0xYourReceiveAddress",
        maxAmountRequired: "100000", // $0.10 USDC (6 decimals)
      },
    ],
    description: "Cool paid API",
  }),
);

app.get("/paid", (req, res) => {
  // req.x402Payment is populated by the middleware
  res.json({
    result: "data",
    payer: req.x402Payment?.payer,
    txHash: req.x402Payment?.txHash,
  });
});

app.listen(3000);

Fastify

import Fastify from "fastify";
import { createFastifyPreHandler } from "@suverselabs/x402-server/fastify";

const app = Fastify();

const x402 = createFastifyPreHandler({
  apiKey: process.env.SUVERSE_PAY_API_KEY!,
  facilitator: "https://facilitator.suverse.io",
  acceptedPayments: [
    {
      scheme: "exact",
      network: "eip155:8453",
      asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      payTo: "0xYourReceiveAddress",
      maxAmountRequired: "100000",
    },
  ],
});

app.post("/paid", { preHandler: x402 }, async (request) => {
  return { result: "data", payer: request.x402Payment?.payer };
});

await app.listen({ port: 3000 });

Options

OptionRequiredDefaultMeaning
apiKeyyes—Resource API key from the Suverse Pay dashboard (sup_live_*).
facilitatoryes—Base URL of the facilitator.
acceptedPaymentsyes—Non-empty array of { scheme, network, asset, payTo, maxAmountRequired }.
descriptionno—Public text shown in the 402 challenge.
x402Versionno2Protocol version to advertise.
settlenotrueIf false, verify only (no on-chain settle).
fetchImplnoglobal fetchInject your own fetch (testing, custom TLS).
loggernosilentPass a pino/winston-style logger for warn/error.
disableAutoDiscovernofalseSkip facilitator-extras auto-discovery; use only the extra you put on each accept entry (v0.2.0 behavior).
facilitatorExtrasCacheTtlMsno3_600_000 (1 h)In-process TTL for the cached /supported response.

Auto-discovery of per-kind extra (v0.3.0+)

The middleware calls GET ${facilitator}/facilitator/supported once at boot, caches the response per facilitator URL, and merges the per-kind extra into every 402 challenge it issues.

What lives in extra depends on the network:

Network familyAuto-discovered fields
SolanafeePayer (the facilitator's co-signer pubkey)
Cosmosfacilitator (grantee bech32), chainId, decimals, symbol
EVMname, version (EIP-712 USDC domain — emitted only by facilitators that publish it)

This means a minimal x402 seller can ship a Solana endpoint with zero hardcoded infrastructure data:

acceptedPayments: [
  {
    scheme: "exact",
    network: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
    asset: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
    payTo: "MyMerchantSolanaAddress",
    maxAmountRequired: "70000",
    // No `extra: { feePayer: ... }` — facilitator fills it in.
  },
]

Merge precedence — seller wins

If you DO set extra on an accept entry, your values override the facilitator's per key:

extra: {
  name: "USD Coin",   // pinned by seller — wins
  version: "2",       // pinned by seller — wins
  // facilitator's other keys flow through (e.g. for compatibility shims)
}

This keeps pre-v0.3.0 configs working unchanged.

Failure mode

If the facilitator is unreachable, returns a non-200, or sends a body the middleware doesn't recognise, auto-discovery falls back silently to seller-only extra (matching v0.2.0 behavior). A warning is logged once per TTL window via opts.logger. The middleware never throws to your boot path because of an unreachable facilitator.

Explicit control

If you'd rather wire auto-discovery into your app's boot sequence yourself, set disableAutoDiscover: true and call the helpers directly:

import { warmFacilitatorCache } from "@suverselabs/x402-server";

await warmFacilitatorCache("https://facilitator.suverse.io");

Or read individual kinds:

import { getFacilitatorExtras } from "@suverselabs/x402-server";

const extra = await getFacilitatorExtras(
  "https://facilitator.suverse.io",
  "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
  "exact",
);
console.log(extra?.feePayer);

Receipt shape

After a successful payment the middleware attaches a PaymentReceipt to req.x402Payment (Express) / request.x402Payment (Fastify):

{
  payer: "0x09939648B56A776de9783eaE750A7fBE725761f1",
  network: "eip155:8453",
  asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
  amount: "100000",
  txHash: "0xabc..." | null, // null in verify-only mode
  raw: { /* full facilitator response */ },
}

Errors

If the facilitator is unreachable or the payment payload is malformed, the middleware writes a 4xx/5xx JSON body with the x402 challenge included so a well-behaved client can immediately retry. The seller's handler is not invoked.

Wire-level errors thrown to your error handler are instances of X402Error — re-export from the root entry:

import { X402Error } from "@suverselabs/x402-server";

app.use((err, req, res, next) => {
  if (err instanceof X402Error) {
    /* log err.code, err.statusCode */
  }
  next(err);
});

Status

  • v0.3 (2026-05-30) — facilitator-extras auto-discovery + signers map consumption from /facilitator/supported. Sellers no longer need to hardcode extra per network.
  • v0.2 (2026-05-30) — x402 v2 ecosystem-client interop (Coinbase-flavour accepts shape, PAYMENT-SIGNATURE header).
  • v0.1 — Express + Fastify adapters. Verify + settle through any facilitator that implements the x402 v2 spec.
  • Python (FastAPI / Starlette / Flask) — next.

See CHANGELOG.md for the full history.

License

Apache-2.0

Keywords

x402

FAQs

Package last updated on 31 May 2026

Related posts