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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@polymerdao/x402-json-rpc-middleware

v0.3.8

Published

x402 payment middleware for JSON RPC endpoints on Hono framework

Readme

x402 JSON RPC Middleware

Payment middleware for JSON RPC endpoints on the Hono framework, built on top of the x402 Payment Protocol.

Overview

This middleware enables monetization of JSON RPC APIs by requiring cryptocurrency payments for specific RPC methods. Unlike the standard x402-hono middleware which works with HTTP routes, this middleware is specifically designed for JSON RPC endpoints where all requests go to a single HTTP endpoint but contain different method names in the request body.

Features

  • Method-based Pricing: Configure pricing per JSON RPC method (e.g., eth_call, eth_getBalance)
  • Static & Dynamic Pricing: Set fixed prices or calculate dynamically based on request context
  • Regex Pattern Matching: Use patterns like debug_.* to match multiple methods
  • Batch Request Support: Handle JSON RPC batch requests with aggregated pricing
  • Multiple Networks: Support payment on multiple blockchain networks per method
  • Custom Facilitators: Configure different facilitators per network or method
  • Multiple Payment Tokens: Accept different tokens for payment
  • Implicit Allow: Unconfigured methods pass through without payment (configurable)
  • Full x402 Integration: Payment verification, settlement, and paywall UI

Installation

npm install x402-json-rpc-middleware
# or
pnpm add x402-json-rpc-middleware
# or
yarn add x402-json-rpc-middleware

Quick Start

import { Hono } from "hono";
import { jsonRpcPaymentMiddleware } from "x402-json-rpc-middleware";

const app = new Hono();

app.use(
  "/rpc",
  jsonRpcPaymentMiddleware({
    payTo: "0xYourAddressHere",
    methods: {
      // Static pricing
      eth_call: {
        price: { amount: "1000" }, // In smallest unit (e.g., 0.001 USDC)
        networks: [{ network: "base-sepolia" }],
      },
      // Regex patterns
      "debug_.*": {
        price: { amount: "5000" },
        networks: [{ network: "base-sepolia" }],
      },
      // Dynamic pricing
      eth_getLogs: {
        price: (context) => {
          // Calculate based on request
          return { amount: "2000" };
        },
        networks: [{ network: "base-sepolia" }],
      },
    },
    facilitator: {
      url: "https://x402-facilitator.coinbase.com",
    },
  }),
);

app.post("/rpc", async (c) => {
  // Your JSON RPC handler
  const body = await c.req.json();
  // Process and return response
});

Configuration

JsonRpcMiddlewareConfig

interface JsonRpcMiddlewareConfig {
  // Recipient address for payments
  payTo: `0x${string}` | string;

  // Method configurations
  methods: {
    [methodPattern: string]: JsonRpcMethodConfig;
  };

  // Global facilitator (optional)
  facilitator?: FacilitatorConfig;

  // Paywall UI configuration (optional)
  paywall?: PaywallConfig;

  // Logger for debugging and monitoring (optional)
  logger?: Logger;
}

JsonRpcMethodConfig

interface JsonRpcMethodConfig {
  // Static price or dynamic pricing function
  price: Money | ((context: Context) => Money | Promise<Money>);

  // Networks this method is available on
  networks: NetworkConfig[];

  // Optional description
  description?: string;

  // Optional timeout in seconds (default: 60)
  maxTimeoutSeconds?: number;
}

NetworkConfig

interface NetworkConfig {
  // Network identifier
  network: Network; // e.g., "base-sepolia", "ethereum-mainnet"

  // Optional custom facilitator for this network
  facilitator?: FacilitatorConfig;

  // Optional allowed payment tokens
  tokens?: Array<{
    address: `0x${string}`;
    decimals: number;
    name: string;
    symbol: string;
  }>;
}

Method Pattern Matching

The middleware supports three types of method patterns:

1. Exact Match

methods: {
  "eth_call": { /* config */ },
  "eth_getBalance": { /* config */ }
}

2. Simple Regex

methods: {
  "debug_.*": { /* matches debug_traceTransaction, debug_traceCall, etc. */ },
  "trace_.*": { /* matches all trace methods */ }
}

3. Full Regex Pattern

methods: {
  "/^(eth_getLogs|eth_getFilterLogs)$/": { /* matches both methods */ },
  "/^eth_(call|estimateGas)$/": { /* matches eth_call and eth_estimateGas */ }
}

Dynamic Pricing

Dynamic pricing allows you to calculate the price based on the request context:

methods: {
  eth_call: {
    price: async (context) => {
      const body = await context.req.json();

      // Calculate based on request parameters
      const complexity = calculateComplexity(body.params);
      const basePrice = BigInt(1000);
      const finalPrice = basePrice * BigInt(complexity);

      return {
        amount: finalPrice.toString(),
        asset: {
          address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
          decimals: 6,
          eip712: { name: "USDC", version: "1" }
        }
      };
    },
    networks: [{ network: "base-sepolia" }]
  }
}

Multiple Networks

Allow clients to pay on their preferred network:

methods: {
  eth_getBalance: {
    price: { amount: "500" },
    networks: [
      {
        network: "base-sepolia",
        // Uses global facilitator
      },
      {
        network: "base-mainnet",
        // Custom facilitator for mainnet
        facilitator: {
          url: "https://mainnet-facilitator.example.com"
        }
      },
      {
        network: "ethereum-sepolia",
        // Different tokens accepted
        tokens: [
          {
            address: "0x...",
            decimals: 6,
            name: "USDC",
            symbol: "USDC"
          }
        ]
      }
    ]
  }
}

Logging

The middleware supports custom logging for debugging and monitoring. By default, the middleware runs silently (no logs). You can provide your own logger implementation that works with your deployment environment.

Logger Interface

interface Logger {
  info(message: string, ...args: unknown[]): void;
  info(obj: Record<string, unknown>, message: string): void;
  warn(message: string, ...args: unknown[]): void;
  warn(obj: Record<string, unknown>, message: string): void;
  error(message: string, ...args: unknown[]): void;
  error(obj: Record<string, unknown>, message: string): void;
  debug(message: string, ...args: unknown[]): void;
  debug(obj: Record<string, unknown>, message: string): void;
}

Configuration Examples

Node.js with Console

const logger = {
  info: (msg: string) => console.log('[INFO]', msg),
  warn: (msg: string) => console.warn('[WARN]', msg),
  error: (msg: string) => console.error('[ERROR]', msg),
  debug: (msg: string) => console.debug('[DEBUG]', msg),
};

app.use("/rpc", jsonRpcPaymentMiddleware({
  payTo: "0x...",
  methods: { /* ... */ },
  logger,
}));

Node.js with Pino

import pino from 'pino';

const logger = pino({
  level: 'info',
  transport: {
    target: 'pino-pretty'
  }
});

app.use("/rpc", jsonRpcPaymentMiddleware({
  payTo: "0x...",
  methods: { /* ... */ },
  logger,
}));

Cloudflare Workers

// Simple console wrapper for Workers
const logger = {
  info: (msg: string) => console.log(msg),
  warn: (msg: string) => console.warn(msg),
  error: (msg: string) => console.error(msg),
  debug: (msg: string) => console.debug(msg),
};

// Or use Workers-compatible logging services
// like Axiom, Logtail, Baselime, etc.

What Gets Logged

The middleware logs:

  • Warnings: Invalid network configurations, failed payment requirement builds
  • Errors: Middleware errors, payment verification failures
  • Info: (Optional) Payment flow events when implemented

Note: The default silent logger ensures production safety. Enable logging only when needed for debugging or monitoring.

Batch Requests

The middleware automatically handles JSON RPC batch requests:

POST /rpc
[
  {"jsonrpc": "2.0", "method": "eth_call", "params": [], "id": 1},
  {"jsonrpc": "2.0", "method": "eth_getBalance", "params": ["0x..."], "id": 2}
]
  • Prices are aggregated across all paid methods
  • All methods must use compatible payment tokens
  • Unconfigured methods in the batch are free (implicit allow)

Payment Flow

1. Initial Request (No Payment)

Client sends JSON RPC request without payment:

curl -X POST http://localhost:3000/rpc \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc": "2.0", "method": "eth_call", "params": [], "id": 1}'

Server responds with 402 Payment Required (following x402 protocol):

{
  "x402Version": 1,
  "accepts": [
    {
      "scheme": "exact",
      "network": "base-sepolia",
      "maxAmountRequired": "1000",
      "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
      "payTo": "0x...",
      "resource": "http://localhost:3000/rpc",
      "description": "JSON RPC call: eth_call",
      "mimeType": "application/json"
    }
  ],
  "totalPrice": {
    "amount": "1000"
  },
  "breakdown": [
    {
      "method": "eth_call",
      "price": { "amount": "1000" }
    }
  ]
}

Note: The response follows the official x402 protocol format with x402Version and accepts fields. The totalPrice and breakdown fields are additional metadata specific to the JSON-RPC context.

2. Request with Payment

Client includes payment in X-PAYMENT header:

curl -X POST http://localhost:3000/rpc \
  -H "Content-Type: application/json" \
  -H "X-PAYMENT: <payment_token>" \
  -d '{"jsonrpc": "2.0", "method": "eth_call", "params": [], "id": 1}'

Server verifies payment and returns result:

{
  "jsonrpc": "2.0",
  "result": "0x...",
  "id": 1
}

Response includes X-PAYMENT-RESPONSE header for settlement.

Error Codes

The middleware uses standard JSON RPC 2.0 error codes for request processing errors:

| Code | Message | Description | |------|---------|-------------| | -32700 | Parse error | Invalid JSON | | -32600 | Invalid Request | Invalid JSON RPC 2.0 format | | -32603 | Internal error | Server error |

Note: Payment-related responses use HTTP 402 status code (not JSON-RPC errors), following the x402 protocol specification. The 402 response contains payment requirements in the body as shown in the Payment Flow section above.

Examples

See the examples directory for complete working examples:

API Reference

Main Function

jsonRpcPaymentMiddleware(config: JsonRpcMiddlewareConfig): MiddlewareHandler

Creates a Hono middleware handler for JSON RPC payment enforcement.

Utility Functions

parseJsonRpcRequest(body: unknown): JsonRpcRequestInput

Parses and validates a JSON RPC 2.0 request.

calculatePrice(matchedMethods: MatchedMethod[], context: Context): Promise<PriceCalculation>

Calculates total price for a set of matched methods.

MethodMatcher

Class for matching JSON RPC methods against configured patterns.

const matcher = new MethodMatcher(methodsConfig);
const match = matcher.match("eth_call");

Types

All TypeScript types are exported for use in your application:

import type {
  JsonRpcMiddlewareConfig,
  JsonRpcMethodConfig,
  NetworkConfig,
  DynamicPriceFn,
  Money,
  // ... and more
} from "x402-json-rpc-middleware";

Best Practices

1. Start with Static Pricing

Begin with static prices and add dynamic pricing only when needed:

methods: {
  "eth_call": { price: { amount: "1000" }, networks: [...] }
}

2. Use Regex Patterns for Method Groups

Group similar methods with regex patterns:

methods: {
  "debug_.*": { price: { amount: "5000" }, networks: [...] },
  "trace_.*": { price: { amount: "10000" }, networks: [...] }
}

3. Provide Multiple Networks

Give users flexibility to pay on their preferred network:

networks: [
  { network: "base-sepolia" },    // Testnet
  { network: "base-mainnet" },    // Mainnet
  { network: "ethereum-mainnet" } // Alternative
]

4. Monitor Performance

Dynamic pricing functions run on every request. Keep them fast:

// Good - Quick calculation
price: (c) => {
  const multiplier = parseInt(c.req.query("tier") || "1");
  return { amount: (1000 * multiplier).toString() };
}

// Avoid - Slow external calls
price: async (c) => {
  // Don't do this on every request
  const price = await fetchPriceFromDatabase();
  return price;
}

5. Set Appropriate Timeouts

Complex operations may need longer timeouts:

methods: {
  "trace_block": {
    price: { amount: "10000" },
    networks: [...],
    maxTimeoutSeconds: 120 // 2 minutes for complex traces
  }
}

Development

# Install dependencies
pnpm install

# Build
pnpm build

# Type check
pnpm typecheck

# Lint
pnpm lint

# Format
pnpm format

# Run examples
pnpm example:basic
pnpm example:advanced

License

Apache-2.0