Skip to main content
There are two ways to implement dynamic pricing depending on your setup.

With in-process handlers

When using in-process handlers (x402Handlers + pricing), handlers receive the resource URL as context and can adjust pricing per request. This is the simplest path — configure base pricing and let the handler do the work.
import { createMiddleware } from "@faremeter/middleware/hono"

app.get(
  "/api/:tier",
  await createMiddleware({
    x402Handlers: [solanaHandler],
    pricing: [
      { amount: "10000", asset: "USDC", recipient: "7xKX...", network: "solana:devnet" },
    ],
  }),
  (c) => c.json({ tier: c.req.param("tier") }),
)
The handler’s getRequirements method receives the resource URL (e.g. /api/premium) and can return different amounts based on the path, query parameters, or any other aspect of the request.

With handleMiddlewareRequest

When you need to vary pricing from the middleware layer itself, 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. Unlike fixed pricing per route, dynamic pricing lets you derive payment requirements from any aspect of the incoming request: headers, query parameters, path, or body. You can also vary:
  • The networks, schemes, and assets accepted
  • The recipient wallet address
  • The facilitator URL (via createRemoteX402Handlers)
Dynamic payment requirements must be idempotent. The same request must produce the same requirements every time, because the client may retry payment against the same requirements.

Server

server.ts
import { Hono, type MiddlewareHandler } from "hono"
import { HTTPException } from "hono/http-exception"
import { serve } from "@hono/node-server"
import { x402Exact as solanaX402Exact } from "@faremeter/info/solana"
import { x402Exact } from "@faremeter/info/evm"
import {
  handleMiddlewareRequest,
  resolveSupportedVersions,
} from "@faremeter/middleware/common"
import { createRemoteX402Handlers } from "@faremeter/middleware"

const createDynamicPricingMiddleware = (): MiddlewareHandler => {
  return async (c, next) => {
    const amount = c.req.query("amount")
    const asset = "USDC"

    // In production, validate that amount is a numeric string.
    if (amount === undefined) {
      throw new HTTPException(400, {
        message: "you need to provide an 'amount' querystring parameter",
      })
    }

    const x402Handlers = createRemoteX402Handlers({
      facilitatorURL: "https://facilitator.corbits.dev",
      accepts: [
        ...solanaX402Exact({
          network: "devnet",
          asset,
          amount,
          payTo: process.env.MERCHANT_ADDRESS,
        }),
        x402Exact({
          network: "base-sepolia",
          asset,
          amount,
          payTo: process.env.EVM_MERCHANT_ADDRESS,
        }),
      ],
    })

    return await handleMiddlewareRequest({
      x402Handlers,
      pricing: [
        { amount, asset, recipient: process.env.MERCHANT_ADDRESS, network: "solana:devnet" },
        { amount, asset, recipient: process.env.EVM_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 settleResult = await context.settle()
        if (!settleResult.success) {
          return settleResult.errorResponse
        }

        // On success, call the next handler and let Hono set the response.
        await next()
      },
    })
  }
}

const app = new Hono()

app.get("/protected", createDynamicPricingMiddleware(), (c) => {
  return c.json({ msg: "success" })
})

// @hono/node-server is required to run Hono as a standalone Node.js server.
serve(app, (info) => {
  console.log(`Listening on http://localhost:${info.port}`)
})

Client

client.ts
import "dotenv/config"
import { payer } from "@faremeter/rides"
import { getLogger } from "@faremeter/logs"

const logger = await getLogger(["client"])

await payer.addLocalWallet(process.env.PAYER_KEYPAIR_PATH)
await payer.addLocalWallet(process.env.EVM_PRIVATE_KEY)

const response = await payer.fetch("http://localhost:3000/protected?amount=1000")
logger.info(JSON.stringify(await response.json()))

Running the example

# Terminal 1
MERCHANT_ADDRESS=7xKXwxRPMo2sUAT5... EVM_MERCHANT_ADDRESS=0x1234... pnpm tsx server.ts

# Terminal 2
PAYER_KEYPAIR_PATH=~/.config/solana/id.json EVM_PRIVATE_KEY=0xabc... pnpm tsx client.ts
The client requests /protected?amount=1000, and the server returns payment requirements for 1000 units (0.001 USDC). Changing the amount query parameter changes the price charged.