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

@parallel-protocol/x402

v0.5.2

Published

Parallel Protocol x402 payment middleware — Express, Next.js, Fastify, Hono

Downloads

494

Readme

@parallel-protocol/x402

HTTP-402 payment middleware for AI agents — Express, Next.js, Fastify, Hono.

Wrap any route with a price tag. When an agent (or any HTTP client) hits the route without a valid payment, it receives a 402 Payment Required response describing exactly what is owed and to whom. The agent signs an EIP-3009 authorization off-chain and retries the request with a payment-signature header; the middleware has the payment verified by the facilitator before the handler runs, and settled on-chain only after the handler returns a 2xx.

Zero smart-contract interaction on the merchant side. The Parallel facilitator verifies signatures and handles all on-chain execution.

Building the agent side instead? @parallel-protocol/x402-fetch is the payer counterpart — a fetch that settles these 402 challenges automatically.


How it works

Agent                        Merchant (this SDK)           Facilitator
  |                                |                             |
  |── GET /api/data ──────────────>|                             |
  |                                | (route matches, no header)  |
  |<─ 402 + payment-required ──────|                             |
  |   (base64 PaymentRequired)     |                             |
  |                                |                             |
  | [agent signs EIP-3009 auth]    |                             |
  |                                |                             |
  |── GET /api/data ──────────────>|                             |
  |   payment-signature: <base64>  |── POST /x402/verify ───────>|
  |                                |<─ { isValid: true } ────────|
  |                                |                             |
  |                                | [handler runs]              |
  |                                |                             |
  |                                |── POST /x402/settle ───────>|
  |                                |<─ { txHash, route, ... } ───|
  |                                |                             |
  |<─ 200 + payment-response ──────|                             |
  |   (base64 settlement proof)    |                             |

Key properties:

  • The handler only runs after the signature is verified.
  • The on-chain settlement happens after the handler produces a 2xx response — the agent is not charged on errors.
  • If settlement fails (on-chain revert, facilitator timeout), the middleware returns 402 and discards the handler's response.
  • Handlers are bounded by a 30-second timeout on Express, Hono and Next.js — a slow handler returns 504 and the agent is not charged. (Fastify manages handler execution itself, so no timeout is applied there.)
  • CORS preflight (OPTIONS) is always passed through without challenge.

Install

npm install @parallel-protocol/x402
# or
bun add @parallel-protocol/x402

Install your framework separately (all are optional peer dependencies):

npm install express       # Express 4 or 5
npm install next          # Next.js 14 or 15
npm install fastify       # Fastify 4 or 5
npm install hono          # Hono 4+

TypeScript consumers should also install viem (peer dependency) — the public types use its Address type. Node ≥ 18 is required.


Quick start

Express

import express from "express";
import { paymentMiddleware } from "@parallel-protocol/x402/express";

const app = express();

app.use(
  paymentMiddleware({
    facilitator: {
      url: "https://agents.parallel.best",
    },
    routes: {
      "/api/data": {
        price: "0.01",          // 0.01 USDp
        network: "base",
        payTo: "0xYourAddress",
      },
    },
  }),
);

app.get("/api/data", (req, res) => {
  res.json({ data: "protected content" });
});

Next.js (App Router)

// app/api/data/route.ts
import { NextResponse } from "next/server";
import { withPayment } from "@parallel-protocol/x402/next";

const config = {
  facilitator: { url: "https://agents.parallel.best" },
  routes: {
    "/api/data": {
      price: "0.10",
      network: "ethereum",
      payTo: "0xYourAddress",
    },
  },
};

export const GET = withPayment(config, async (req) => {
  return NextResponse.json({ data: "protected content" });
});

Fastify

import Fastify from "fastify";
import { paymentMiddleware } from "@parallel-protocol/x402/fastify";

const app = Fastify();

await app.register(
  paymentMiddleware({
    facilitator: { url: "https://agents.parallel.best" },
    routes: {
      "/api/data": {
        price: "0.05",
        network: "avalanche",
        payTo: "0xYourAddress",
      },
    },
  }),
);

app.get("/api/data", async () => {
  return { data: "protected content" };
});

Hono

import { Hono } from "hono";
import { paymentMiddleware } from "@parallel-protocol/x402/hono";

const app = new Hono();

app.use(
  paymentMiddleware({
    facilitator: { url: "https://agents.parallel.best" },
    routes: {
      "/api/data": {
        price: "0.01",
        network: "base",
        payTo: "0xYourAddress",
      },
    },
  }),
);

app.get("/api/data", (c) => c.json({ data: "protected content" }));

Configuration

PaymentMiddlewareConfig

interface PaymentMiddlewareConfig {
  facilitator: FacilitatorConfig;
  routes: Record<string, RouteConfig>;
}

FacilitatorConfig

| Field | Type | Required | Description | |-------|------|----------|-------------| | url | string | Yes | Base URL of the Parallel facilitator (e.g. https://agents.parallel.best). The SDK internally appends /x402/verify and /x402/settle to this value. | | apiKey | string | No | Optional API key, sent as the X-API-Key header on facilitator requests. |

RouteConfig

| Field | Type | Required | Description | |-------|------|----------|-------------| | price | string \| bigint | Yes | Amount required. A string like "0.01" is parsed with each accepted token's own decimals (or with decimals when set explicitly). Pass a bigint to skip parsing. | | decimals | number | No | Override for the decimals used to parse a string price. By default each accepted token is priced with its own decimals from the @parallel-protocol/chains catalog — a "0.01" price is correct whether paid in USDp (18) or USDC (6), no configuration. Only needed for a custom token outside the catalog; without it, an unknown token raises X402ConfigError rather than being silently guessed. | | network | string | Yes | Chain slug: "ethereum", "base", "avalanche", "hyperevm". | | payTo | Address | Yes | The merchant's receiving address. | | acceptedTokens | Address[] | No | List of accepted token addresses. Defaults to [USDp, USDC, sUSDp] for known networks (see Default tokens). | | description | string | No | Human-readable description of the resource, included in the 402 body. |

Route matching uses longest prefix wins: /api/v1/users will match a /api/v1 route config before a /api one.


Default tokens

For the four natively supported networks, acceptedTokens defaults to [USDp, USDC, sUSDp]:

| Network | USDp | USDC | sUSDp | |---------|------|------|-------| | ethereum | 0x9B3a8f7CEC208e247d97dEE13313690977e24459 | 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 | 0xd3a452B305C8285c0Dd7b8537665c734d3D279eF | | base | 0x76A9A0062ec6712b99B4f63bD2b4270185759dd5 | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 | 0x472eD57b376fE400259FB28e5C46eB53f0E3e7E7 | | avalanche | 0x9eE1963f05553eF838604Dd39403be21ceF26AA4 | 0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E | 0x9d92c21205383651610f90722131655a5b8ed3e0 | | hyperevm | 0xBE65F0F410A72BeC163dC65d46c83699e957D588 | 0xb88339cb7199b77e23db6e890353e22632ba630f | 0x9B3a8f7CEC208e247d97dEE13313690977e24459 |

For any other network (from the full Parallel 25-chain catalog), you must provide acceptedTokens explicitly — the middleware throws X402ConfigError on the first request to that route otherwise.

You can also import the token list directly:

import { PARALLEL_TOKENS, getDefaultAcceptedTokens } from "@parallel-protocol/x402";

const baseTokens = PARALLEL_TOKENS.base;
// { usdp: "0x76A9...", susdp: "0x472e...", usdc: "0x8335...", USDS: "0x820C...", sUSDS: "0x5875..." }

const defaults = getDefaultAcceptedTokens("base");
// ["0x76A9...", "0x8335...", "0x472e..."]  (USDp + USDC + sUSDp)

sUSDp is included in the defaults (routes H, I, J, K, L are enabled out of the box). To opt out and accept only USDp and USDC, pass acceptedTokens explicitly:

import { PARALLEL_TOKENS } from "@parallel-protocol/x402";

routes: {
  "/api/data": {
    price: "0.01",
    network: "base",
    payTo: "0xYourAddress",
    acceptedTokens: [
      PARALLEL_TOKENS.base.usdp,
      PARALLEL_TOKENS.base.usdc,
      // sUSDp omitted → routes H, I, J, K, L disabled for this route
    ],
  },
}

Accepting Parallelizer collateral payments: Each supported network exposes additional collateral tokens whitelisted in the Parallelizer contract. They are available in PARALLEL_TOKENS but excluded from the defaults. Add them to acceptedTokens to let merchants receive them directly:

| Network | Token | Key | Yield-bearing | |---------|-------|-----|---------------| | ethereum | frxUSD | PARALLEL_TOKENS.ethereum.frxUSD | No | | ethereum | sfrxUSD | PARALLEL_TOKENS.ethereum.sfrxUSD | Yes | | ethereum | USDe | PARALLEL_TOKENS.ethereum.USDe | No | | ethereum | sUSDe | PARALLEL_TOKENS.ethereum.sUSDe | Yes | | base | USDS | PARALLEL_TOKENS.base.USDS | No | | base | sUSDS | PARALLEL_TOKENS.base.sUSDS | Yes | | avalanche | ygamiUSDC | PARALLEL_TOKENS.avalanche.ygamiUSDC | Yes | | hyperevm | USDe | PARALLEL_TOKENS.hyperevm.USDe | No | | hyperevm | sUSDe | PARALLEL_TOKENS.hyperevm.sUSDe | Yes |

import { PARALLEL_TOKENS } from "@parallel-protocol/x402";

routes: {
  "/api/data": {
    price: "0.01",
    network: "ethereum",
    payTo: "0xYourAddress",
    acceptedTokens: [
      PARALLEL_TOKENS.ethereum.usdp,
      PARALLEL_TOKENS.ethereum.usdc,
      PARALLEL_TOKENS.ethereum.sUSDe,   // opt-in → agents can pay with sUSDe
      PARALLEL_TOKENS.ethereum.sfrxUSD, // opt-in → agents can pay with sfrxUSD
    ],
  },
}

Note: the facilitator validates that tokenOut is a whitelisted collateral on the target chain. If you add an address that is not in the Parallelizer's collateral list, payments to that route will be rejected at settlement.


Payment headers

402 response: payment-required

When no valid payment is present, the middleware returns HTTP 402 with a payment-required header containing a base64-encoded PaymentRequired object. The same JSON is also sent as the response body, and access-control-expose-headers: payment-required is set so browsers can read the header:

interface PaymentRequired {
  x402Version: 2;
  error?: string;           // present on re-challenge (e.g. "INVALID_SIGNATURE")
  resource: {
    url: string;
    description?: string;
    mimeType: "application/json";
  };
  accepts: Array<{         // one entry per accepted token
    scheme: "exact";
    network: string;        // EIP-155 format: "eip155:8453"
    asset: string;          // token address
    amount: string;         // smallest unit of `asset`, per its own decimals
    payTo: string;
    maxTimeoutSeconds: 300;
    extra: { decimals: number };  // decimals of `asset` — payers need not guess
  }>;
}

Request header: payment-signature

The agent attaches a payment-signature (or X-PAYMENT) header with a base64-encoded SignedPaymentPayload:

// Minimal Route A payload (USDp transfer)
const signedPayload = {
  scheme: "exact",
  network: "base",
  method: "transferWithAuthorization",
  payload: {
    signature: "0x...",
    authorization: {
      from: "0xAgentAddress",
      to:   "0xMerchantAddress",
      value: "10000000000000000",  // 0.01 USDp in wei
      validAfter:  "0",
      validBefore: "1234567890",   // Unix timestamp
      nonce: "0x...",
    },
  },
};

const header = Buffer.from(JSON.stringify(signedPayload)).toString("base64");

See @parallel-protocol/payment-core for EIP-3009 signing helpers.

200 response: payment-response

On success, the middleware sets a payment-response header with a base64-encoded settlement confirmation:

interface PaymentConfirmation {
  success: true;
  txHash: string;
  route: string;        // "A", "B", "C", ...
  chain: string;        // "base"
  gasSponsored: boolean;
  networkId: string;    // "eip155:8453"
}

The access-control-expose-headers header is set automatically so browsers can read payment-response.


Payment routes

The facilitator supports 12 settlement routes. The route is selected automatically based on the signed payload:

| Route | Credential method(s) | tokenIn (agent pays) | tokenOut (merchant receives) | Gas est. | Notes | |-------|--------------------------|---------------------|------------------------------|----------|-------| | A | transferWithAuthorization | USDp | USDp | ~65K | Direct USDp transfer | | B | swapExactOutputWithAuthorization | USDp | USDC (exact) | ~120K | Parallelizer swaps agent's USDp → exact USDC for merchant | | C | depositWithAuthorization | USDp | sUSDp | ~90K | Agent's USDp deposited into savings vault; merchant receives sUSDp shares | | D | swapExactOutputWithAuthorization | USDp | non-USDC backing collateral (exact) | ~120K | Same as B but for other Parallelizer collaterals | | E | swapExactInputWithAuthorization | USDC (exact) | USDp (min) | ~120K | Agent pays exact USDC; Parallelizer delivers minimum USDp to merchant | | F | transferWithAuthorization | USDC (or any EIP-3009 token) | same token | ~65K | Direct EIP-3009 token push | | G | swapExactInputWithAuthorization | USDC (exact) | sUSDp | ~200K | USDC → USDp swap, then USDp deposited as sUSDp for merchant (atomic multicall3) | | H | redeemWithAuthorization | sUSDp shares | USDp | ~130K | Agent burns sUSDp shares; merchant receives USDp assets | | I | redeemWithAuthorization + swapExactOutputWithAuthorization | sUSDp | USDC (exact) | ~180K | Agent redeems sUSDp → USDp, then swaps USDp → exact USDC for merchant (atomic multicall3) | | J | transferWithAuthorization | sUSDp | sUSDp | ~65K | Direct sUSDp share transfer | | K | redeemWithAuthorization + swapExactOutputWithAuthorization | sUSDp | non-USDC backing (exact) | ~180K | Same as I but for other Parallelizer collaterals (atomic multicall3) | | L | partialRedeemWithAuthorization | sUSDp (partial) + USDp | USDp or backing collateral | ~180K | Partial sUSDp redeem combined with existing USDp; delivers to merchant (atomic multicall3) |

Chain requirements:

  • Routes B, D, E, G, I, K, L require the Parallelizer to be deployed on the target chain (Ethereum, Base, Avalanche, HyperEVM).
  • Routes C, G, H, I, J, K, L require sUSDp (savings module) to be deployed on the target chain.
  • Route A and F work on all chains where the respective token is deployed.
  • Gas sponsorship is decided by the facilitator per payment and reported as gasSponsored in the settlement receipt.

Exports

Main entry (@parallel-protocol/x402)

import {
  // Middleware (framework-agnostic)
  createPaymentGate,
  createPaymentMiddleware,

  // Facilitator client
  FacilitatorClient,

  // Errors
  X402ConfigError,
  X402RuntimeError,
  X402_ERROR_CODES,
  type X402ErrorCode,

  // Tokens
  PARALLEL_TOKENS,
  getDefaultAcceptedTokens,
  type ParallelNetwork,

  // Types
  type FacilitatorConfig,
  type FacilitatorResponse,
  type HTTPAdapter,
  type MiddlewareResult,
  type PassResult,
  type PaymentConfirmation,
  type PaymentFailure,
  type PaymentGateResult,
  type PaymentMiddlewareConfig,
  type PaymentRequired,
  type PaymentRequirements,
  type ResourceInfo,
  type RouteConfig,
  type RunHandlerResult,

  // Utils
  encodeBase64,
  parsePrice,
  toEip155Network,
  validateAddress,
} from "@parallel-protocol/x402";

Framework adapters

| Import path | Export | Framework | |-------------|--------|-----------| | @parallel-protocol/x402/express | paymentMiddleware(config) | Express 4/5 | | @parallel-protocol/x402/next | withPayment(config, handler) | Next.js 14/15 | | @parallel-protocol/x402/fastify | paymentMiddleware(config) | Fastify 4/5 | | @parallel-protocol/x402/hono | paymentMiddleware(config) | Hono 4+ |

Each subpath also exports its adapter class (ExpressAdapter, NextAdapter, FastifyAdapter, HonoAdapter) for use with createPaymentGate.


Advanced: createPaymentGate

For frameworks not covered above, or for custom integrations, you can use the lower-level createPaymentGate function:

import { createPaymentGate } from "@parallel-protocol/x402";

const gate = createPaymentGate(config);

// In your request handler:
const result = await gate({
  getHeader: (name) => request.headers[name],
  getMethod: () => request.method,
  getPath:   () => request.path,
  getUrl:    () => request.url,
});

if (result.type === "pass") {
  // Route not configured for payment, proceed normally
} else if (result.type === "error") {
  // Send 402: result.result.status / result.result.headers / result.result.body
} else {
  // result.type === "verified"
  // Run your handler...
  const handlerStatus = await runHandler();

  if (handlerStatus >= 200 && handlerStatus < 300) {
    const settlement = await result.settle();
    if (settlement.success) {
      // settlement.txHash / settlement.route / settlement.gasSponsored
    }
  }
}

createPaymentGate only verifies; it does not run the handler. Use createPaymentMiddleware if you want the full verify → run → settle flow managed automatically.


FacilitatorClient

Low-level client for the facilitator's x402 endpoints:

import { FacilitatorClient, PARALLEL_TOKENS, type FacilitatorPaymentRequirements } from "@parallel-protocol/x402";

const client = new FacilitatorClient({
  url: "https://agents.parallel.best",
});

const requirements: FacilitatorPaymentRequirements = {
  maxAmountRequired: "10000000000000000", // 0.01 USDp, smallest unit
  asset: PARALLEL_TOKENS.base.usdp,
  payTo: "0xYourAddress",
  network: "base",                        // plain chain slug
};

// Phase 1: verify signature + reserve nonce (no on-chain tx)
await client.verify(paymentHeader, requirements); // throws X402RuntimeError on failure

// Phase 2: submit on-chain, return settlement result
const result = await client.settle(paymentHeader, requirements);
if (result.success) {
  console.log(result.txHash);       // on-chain tx hash
  console.log(result.route);        // "A", "B", ...
  console.log(result.gasSponsored); // boolean
} else {
  console.error(result.error.code); // e.g. "SUBMISSION_FAILED"
}

verify uses a 10-second timeout. settle uses a 60-second timeout to accommodate on-chain confirmation latency. verify throws on any failure; settle returns a FacilitatorResponse union on facilitator errors and throws X402RuntimeError on connectivity issues, an undecodable payment header, or a malformed facilitator response.

A single-call client.pay(paymentHeader, requirements) (POST /x402/pay) also exists — it verifies and settles in one round trip, without the two-phase guarantees the middleware relies on.


Error codes

Thrown by the SDK

| Error class | When | |-------------|------| | X402ConfigError | Invalid config at construction (bad payTo, bad facilitator URL, empty routes, non-positive price) — plus, on the first request to a route, an unknown network with no acceptedTokens, or an accepted token whose decimals are neither in the catalog nor set via decimals | | X402RuntimeError | Runtime failure; .code is one of the SDK codes below, or a facilitator error code propagated verbatim (see next table) |

| X402RuntimeError.code | Meaning | |-------------------------|---------| | FACILITATOR_UNAVAILABLE | Facilitator timed out (10s on verify, 60s on settle) or was unreachable | | FACILITATOR_INVALID_RESPONSE | Facilitator returned an unexpected response shape | | INVALID_PAYMENT | payment-signature / X-PAYMENT header could not be base64-decoded |

Propagated from the facilitator

When the facilitator rejects a payment, its error code is forwarded as the error field on the re-issued 402 body. Common values:

| Code | Meaning | |------|---------| | INVALID_SIGNATURE | EIP-712 signature recovery failed | | INVALID_NONCE | Nonce already used (replay detected) | | PAYMENT_EXPIRED | validBefore has passed | | INSUFFICIENT_AMOUNT | Signed amount < required amount | | NETWORK_MISMATCH | Payload chain ≠ route config chain | | RATE_LIMIT_EXCEEDED | Signer has exceeded the hourly transaction limit | | PARALLELIZER_PAUSED | On-chain router temporarily paused (swap routes only) |


Utilities

import {
  parsePrice,
  validateAddress,
  toEip155Network,
  encodeBase64,
  getDefaultAcceptedTokens,
  PARALLEL_TOKENS,
} from "@parallel-protocol/x402";

| Function | Signature | Description | |----------|-----------|-------------| | parsePrice | (price: string \| bigint, decimals?: number) => bigint | Parse "0.01"10000000000000000n (18 decimals by default) | | validateAddress | (address: string) => address is Address | Check 0x + 40-hex-char format | | toEip155Network | (network: string) => string | "base""eip155:8453" | | encodeBase64 | (obj: unknown) => string | JSON.stringify → base64 | | getDefaultAcceptedTokens | (network: string) => [Address, Address, Address] \| undefined | [USDp, USDC, sUSDp] for known networks |


Comparison with MPP

| | @parallel-protocol/x402 | @parallel-protocol/mpp | |-|--------------------------|--------------------------| | Challenge status | 402 | 402 | | Challenge header | payment-required: <base64> | WWW-Authenticate: Payment … | | Credential header | payment-signature: <base64> | Authorization: Payment <base64url> | | Receipt header | payment-response: <base64> | Payment-Receipt: <base64url> | | Token per route | list (acceptedTokens) | single (currency) | | Decimals | per accepted token (chains catalog) | 6 (fixed default) | | Auth standard | x402 / Coinbase | RFC 9110 Payment auth-scheme | | Facilitator endpoints | /x402/verify, /x402/settle | /mpp/verify, /mpp/settle | | On-chain routes | A–L | A–L (same) |

Both protocols share the same facilitator backend, the same SignedPaymentPayload authorization format, and the same settlement routes.


Related packages

| Package | Purpose | |---------|---------| | @parallel-protocol/payment-core | EIP-3009 authorization builders and EIP-712 signing helpers (agent-side) | | @parallel-protocol/x402-types | The facilitator's HTTP wire contract (Zod schemas) | | @parallel-protocol/mpp | Machine Payments Protocol middleware (IETF Payment auth-scheme) | | @parallel-protocol/chains | 25-chain catalog with contract addresses |


License

MIT © Parallel Protocol