Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Seller side (x402-server) · Boson Protocol
Skip to content

Seller side (x402-server)

x402-server is a framework-agnostic SDK for building x402b-enabled HTTP services. x402-server-express is the Express adapter.

Express

import express from "express"
import { createX402bServerExpress } from "@bosonprotocol/x402-server-express"
import { ethers } from "ethers"
 
const wallet = new ethers.Wallet(process.env.SELLER_PRIVATE_KEY!)
const app = express()
 
const x402 = createX402bServerExpress({
  network: "polygon",
  chainId: 137,
  escrow: {
    configId: "production-137-0",
    sellerId: "12",
    productUuid: "ec00f1a5-2c96-4d22-9d80-2a7c12345678",
  },
  signer: wallet,
  facilitator: { url: "https://facilitator.bosonprotocol.io" },
})
 
app.get("/api/inference", x402.middleware(), async (req, res) => {
  // req.x402 is populated: { exchangeId, buyer, paidAt }
  const result = await runInference(req.query.prompt)
  res.json(result)
})
 
app.listen(3000)

What the middleware does

  1. Look for X-PAYMENT header.
  2. If missing → return 402 with the escrow option built from your config.
  3. If present → validate (signature, amount, expiry, token).
  4. POST to the facilitator → wait for on-chain settlement.
  5. Populate req.x402 with exchange info.
  6. Call your handler.

Configuring the escrow option

Every 402 response advertises one or more accepts payment options. escrow is the one this stack adds.

escrow: {
  configId,
  sellerId,
  productUuid,
  // Optional: dynamic price
  amount: "1000000",                  // 1 USDC, in token base units
  asset: "0x...",                     // USDC address on the chain
  // Optional: fulfillment options to advertise
  fulfillment: { channels: ["inline", "email"] },
}

For dynamic pricing, compute these at request time inside your handler.

FullOffer signing hook

When the offer isn't pre-published on-chain (non-listed), the server must sign a FullOffer for the buyer to commit against. The factory takes a signFullOffer hook:

signFullOffer: async (offerDraft) => {
  // … your custom logic, then …
  return await sdk.signFullOffer(offerDraft)
}

Common gotchas

  • CORS. The 402 response must include CORS headers if your client is browser-based.
  • Idempotency. A retried request with the same X-PAYMENT must not double-charge. Cache (paymentHash) → exchangeId.
  • Facilitator outage. If the facilitator is down, the middleware returns 503. Have a circuit breaker.

Next