<!-- Generated raw Markdown for /quickstarts/provider; product pin 638fa1e9ca7face19a442cef43754f0d627c7705. -->

A SubEtha provider is a normal x402 resource server: the **official**
`@x402/express` middleware does the 402/verify/settle choreography, and the
`@subetha/provider` plugin supplies the `subetha-zerc20` scheme. The provider
process holds **no chain keys and never touches the chain** — everything
settlement-shaped is delegated to an **external facilitator over HTTP**.
Everything on this page targets the **local, non-production** stack; the code
mirrors the pinned protocol spec and the in-repo demo provider, and the
expectations at the end are what those sources define.

## 1. Install (published packages only)

```bash
npm install @subetha/provider @x402/core @x402/express express
```

- `@subetha/provider` (Apache-2.0, ESM, Node.js >= 20) brings
  `@subetha/x402-scheme` with it as a regular dependency.
- `@x402/core` and `@x402/express` are the **official x402 packages**; the
  pinned product commit builds its demo provider against `^2.17.0`.
- There is **no facilitator package to install**. `@subetha/facilitator` is
  private/workspace-internal — the facilitator is an external HTTP service
  you point a URL at, run from product-repo source
  ([facilitator quickstart](/quickstarts/facilitator/)).

## 2. Point at an external facilitator

```bash
# Local facilitator from the facilitator quickstart (loopback, port 4032).
export FACILITATOR_URL=http://127.0.0.1:4032
```

`SubethaFacilitatorClient` implements the official `FacilitatorClient`
interface over HTTP (`/verify`, `/settle`, `/supported`) plus SubEtha's
`POST /challenge` extension — the call that mints a fresh one-time burn
address per offer. That extension exists because only the facilitator (the
seed holder) can derive a burn address; a resource server cannot mint its own
`PaymentRequirements` the way vanilla x402 sellers do.

## 3. Mount the scheme server and middleware

This is the provider wiring the pinned
[`docs/PROTOCOL.md` §5](https://github.com/peaceandwhisky/SubEtha/blob/638fa1e9ca7face19a442cef43754f0d627c7705/docs/PROTOCOL.md)
defines, arranged as a runnable Express app:

```ts
  SubethaFacilitatorClient,
  SubethaSchemeServer,
  subethaAccepts,
  subethaHandler,
} from "@subetha/provider";

const FACILITATOR_URL = process.env.FACILITATOR_URL ?? "http://127.0.0.1:4032";

const facilitator = new SubethaFacilitatorClient(FACILITATOR_URL);
const scheme = new SubethaSchemeServer(facilitator);
const server = new x402ResourceServer([facilitator]);
server.register("eip155:31337", scheme);
await server.initialize(); // hard-fails unless /supported advertises the kind

const app = express();
app.use(express.json());
app.use(
  paymentMiddleware(
    {
      "/api/*": {
        accepts: subethaAccepts(scheme, { network: "eip155:31337", price: "1000" }),
      },
    },
    server,
  ),
);

// The handler returns a value and never touches `res` — subethaHandler makes
// the single response commit after the middleware settles the payment.
app.post("/api/complete", subethaHandler(async (req) => ({ body: { answer: 42 } })));

app.listen(4031);
```

Initialization notes from the pinned sources:

- `await server.initialize()` syncs the facilitator's `/supported` and
  **fails fast** when the `subetha-zerc20` kind is not advertised — start the
  facilitator first.
- `price` is the **list price in token base units**; the quoted `amount`
  (including any facilitator fee) comes from the facilitator per request.
- `subethaAccepts(schemeServer, { network, price })` builds the route's
  `accepts` entry with a dynamic `payTo`: a fresh one-time burn address is
  minted per unpaid 402, and the client's `accepted.payTo` is echoed (not
  re-minted) on the paying request so the middleware's offer match is
  deterministic. `SUBETHA_PLACEHOLDER_PAYTO` exists only for static
  quote-only route configs — real payment routes use `subethaAccepts`.
- A handler that throws becomes a 500 and the payment is **not** settled; on
  a failed settle the buffered success body is discarded and the client gets
  a 402 — nothing is served unpaid.

## 4. What to expect, and current limits

With the facilitator up, an unpaid `POST /api/complete` draws a **402** whose
offer carries only the one-time burn address; a paying client's retry settles
(**accepted** — response served with the `PAYMENT-RESPONSE` header) and the
proof-gated mint **finalizes later, asynchronously**
([accepted vs finalized](/concepts/accepted-vs-finalized/)).

Limits stated at the pinned commit:

- **Single-process assumption.** Live offers are cached in memory
  (`maxCachedOffers` default 5000); multi-instance deployments would need a
  shared offer cache. After a restart, in-flight offers fail the match and
  clients simply re-pay against a fresh 402.
- **Verify is point-in-time (TOCTOU).** The middleware runs
  verify → handler → settle; a payer can invalidate its payment between
  verify and settle, in which case settle fails after your handler already
  ran. Funds are never at risk (the failed settle yields a 402, nothing is
  served), but expensive handlers deserve ordinary rate limiting — the pinned
  release supports only low-cost, non-streaming, side-effect-free handlers.
- **The provider holds no keys.** The payout destination lives
  facilitator-side (`settlement.treasury` or the FeeSplit deployment); the
  route config carries no payout address or secret.

## Next steps

- [Facilitator quickstart](/quickstarts/facilitator/) — run the external
  service this page points at.
- [Local end-to-end tutorial](/tutorial/local-flow/) — the full flow with a
  payer on the other end.
- [TypeScript API reference](/reference/typescript/) — the complete
  `@subetha/provider` surface, including offer validation helpers.
