npm package: @polymerdao/x402-json-rpc-middleware
Description: x402 payment middleware for JSON RPC endpoints on Hono framework
Author: Polymer Labs
Latest version: 0.3.8

# x402 JSON RPC Middleware

Payment middleware for JSON RPC endpoints on the Hono framework, built on top of the [x402 Payment Protocol](https://github.com/coinbase/x402).

## 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

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

## Quick Start

```typescript
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

```typescript
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

```typescript
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

```typescript
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
```typescript
methods: {
  "eth_call": { /* config */ },
  "eth_getBalance": { /* config */ }
}
```

### 2. Simple Regex
```typescript
methods: {
  "debug_.*": { /* matches debug_traceTransaction, debug_traceCall, etc. */ },
  "trace_.*": { /* matches all trace methods */ }
}
```

### 3. Full Regex Pattern
```typescript
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:

```typescript
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:

```typescript
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

```typescript
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

```typescript
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

```typescript
import pino from 'pino';

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

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

#### Cloudflare Workers

```typescript
// 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:

```bash
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:

```bash
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):

```json
{
  "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:

```bash
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:

```json
{
  "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](./examples) directory for complete working examples:

- **[Basic Example](./examples/basic)**: Static pricing, regex patterns, single network
- **[Advanced Example](./examples/advanced)**: Dynamic pricing, multiple networks, custom facilitators

## 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.

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

### Types

All TypeScript types are exported for use in your application:

```typescript
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:

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

### 2. Use Regex Patterns for Method Groups

Group similar methods with regex patterns:

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

### 3. Provide Multiple Networks

Give users flexibility to pay on their preferred network:

```typescript
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:

```typescript
// 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:

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

## Development

```bash
# 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