<!-- Generated raw Markdown for /tutorial/local-flow; product pin 638fa1e9ca7face19a442cef43754f0d627c7705. -->

This walkthrough takes one paid API call through the whole **local,
non-production** SubEtha flow. The success graph you are aiming for:

```
402 (offer: one-time burn address)
  → payment (zERC20 burn — signed permit, or self-broadcast)
  → accepted (settle succeeds; the resource is served)     ← the request path ends here
  → finalized (proof-gated mint to the treasury)           ← later, asynchronous
```

**Accepted** and **finalized** are different states on purpose. `accepted` is
the synchronous outcome of the HTTP request: the facilitator's `/settle`
succeeded and the provider served the resource. `finalized` happens **only
later**, when the facilitator's finalize loop gets the transfer root proved
and drives the proof-gated mint — no HTTP response in the flow ever says
`finalized`. If this distinction is new, read
[accepted vs finalized](/concepts/accepted-vs-finalized/) first.

:::caution[Verification status of this page]
The steps and output blocks below are **expected behavior taken from the
pinned product sources** (the product's tutorial, the demo README, and the
operator runbook); they were not re-executed as part of publishing this page.
The canonical, always-current execution path is the product repository's own
runbook:
[`docs/RUNNING-THE-STACK.md`](https://github.com/peaceandwhisky/SubEtha/blob/638fa1e9ca7face19a442cef43754f0d627c7705/docs/RUNNING-THE-STACK.md)
and
[`docs/TUTORIAL.md`](https://github.com/peaceandwhisky/SubEtha/blob/638fa1e9ca7face19a442cef43754f0d627c7705/docs/TUTORIAL.md).
If a step disagrees with the runbook, the runbook wins.
:::

## Prerequisites

Explicitly, before anything pays:

| requirement | why | reference |
|---|---|---|
| Node.js >= 20 and pnpm | build and run the TypeScript workspace | product [`docs/TUTORIAL.md`](https://github.com/peaceandwhisky/SubEtha/blob/638fa1e9ca7face19a442cef43754f0d627c7705/docs/TUTORIAL.md) |
| Foundry (`anvil`, `cast`, `forge`) | the local chain and chain inspection; PermitBurner deploys | product [`docs/FACILITATOR.md`](https://github.com/peaceandwhisky/SubEtha/blob/638fa1e9ca7face19a442cef43754f0d627c7705/docs/FACILITATOR.md) |
| A product-repo checkout | the demo, the facilitator, and the provider all run from source | `git clone https://github.com/peaceandwhisky/SubEtha` |
| The **built** local zERC20 stack at `../zerc20-work` | SubEtha settles against the official toolchain (BUSL-1.1, referenced via a local sibling link — never vendored) | [`docs/BUILDING-THE-STACK.md`](https://github.com/peaceandwhisky/SubEtha/blob/638fa1e9ca7face19a442cef43754f0d627c7705/docs/BUILDING-THE-STACK.md) |
| A payer account pre-funded with zERC20 | the local deploy mints test tokens to it; there is no faucet | [`docs/RUNNING-THE-STACK.md`](https://github.com/peaceandwhisky/SubEtha/blob/638fa1e9ca7face19a442cef43754f0d627c7705/docs/RUNNING-THE-STACK.md) |

Everything runs on loopback, on chain **31337**, with anvil's published dev
keys — dev-only values that are never legitimate anywhere else.

## Step 1 — bring up the zERC20 stack

Per the runbook, four services must be up (start order: anvil → postgres →
decider → indexer):

| service | port |
|---|---|
| anvil (chain 31337) | `:8545` |
| indexer | `:8080` |
| decider-prover | `:8081` |
| postgres ×2 (docker) | `:5432`, `:5433` |

The exact start commands, log locations, health checks
(`latestProvedIndex >= 1`, payer balance `> 0`), and recovery procedures are
the runbook's job — follow
[`docs/RUNNING-THE-STACK.md`](https://github.com/peaceandwhisky/SubEtha/blob/638fa1e9ca7face19a442cef43754f0d627c7705/docs/RUNNING-THE-STACK.md)
directly rather than a copy here.

## Step 2 — install and build the workspace

From the product repo root:

```bash
pnpm install
pnpm -r build
```

## Step 3 — fail-fast prerequisite check

```bash
pnpm --filter @subetha/demo-official run deploy-check
```

Expected: exit 0 when the stack is reachable and the payer holds zERC20.
When something is missing (anvil/indexer/decider down, payer unfunded) it
fails with a message pointing back at the runbook — fix and re-run before
going further.

## Step 4 — run the flow

The in-repo demo drives one payment end to end in a single process — payer,
provider (`:4031`), and facilitator logic wired together:

```bash
pnpm --filter @subetha/demo-official run run
```

The root prove takes ~1–2 minutes, so the finalized state is **not** instant.
Expected tail on success (from the pinned demo README):

```
✓ F1: treasury / fullBurnAddress / generalRecipient absent from payer-facing 402
✓ finalized: minted 1234 to treasury, tx=0x…
treasury balance: 0 → 1234 (delta=1234)
✅ END-TO-END SUCCESS — private x402 payment settled via official @zerc20/sdk.
```

### Reading the output against the success graph

- **402** — the run's first request is unpaid; the offer's `payTo` is a
  fresh one-time burn address. The `✓ F1` line asserts the payer-facing 402
  carried **only** that burn address — never the treasury. (What this does
  and does not hide is the subject of the
  [privacy caveats](/concepts/privacy/); amounts, for one, are public.)
- **payment → accepted** — the payer pays and re-sends; settle succeeds and
  the resource is served. This is the end of the request path: at this
  moment the payment is accepted, **not** finalized.
- **finalized** — only after the transfer root is proved does the
  `✓ finalized: minted … to treasury` line appear, followed by the treasury
  balance delta. That is the asynchronous half completing, minutes after
  `accepted`.

### Optional: the gasless variant

By default the demo's payer broadcasts the burn itself and pays its own gas.
To run the same flow gasless (payer only signs; the facilitator broadcasts),
deploy SubEtha's `PermitBurner` helper once and pass `PERMIT_MODE=true` —
the exact `forge script` command and env values are in the pinned
[`docs/TUTORIAL.md` §3](https://github.com/peaceandwhisky/SubEtha/blob/638fa1e9ca7face19a442cef43754f0d627c7705/docs/TUTORIAL.md).
The expected marker line for that variant is
`✓ gasless: payer native balance unchanged`.

## The same flow, component by component

The one-command demo is the fastest proof. To run the roles as separate
processes — the shape a real integration takes — compose the quickstarts, in
this order:

1. **[Facilitator](/quickstarts/facilitator/)** — config with
   `profile = "local"`, `check`, then `start` (loopback `:4032`). It must be
   up first: the provider hard-fails at initialization without its
   `/supported`.
2. **[Provider](/quickstarts/provider/)** — Express app with the official
   middleware and `subethaAccepts`, listening on `:4031`.
3. **A payer** — [TypeScript](/quickstarts/payer-typescript/) or
   [Python](/quickstarts/payer-python/), pointed at
   `http://127.0.0.1:4031/api/complete`.

Then watch the same graph: the payer's first request draws the 402, the paid
retry comes back accepted with the resource, and the facilitator's finalize
loop (every 15 s by default) reports the mint once the root is proved.

## Where to go next

- [Accepted vs finalized](/concepts/accepted-vs-finalized/) — the two-phase
  settlement model this tutorial just demonstrated.
- [Wire protocol reference](/reference/protocol/) — the exact headers,
  payloads, and settle semantics on the wire.
- [Trust & Limits](/trust/) — what you trusted while running this, and what
  the construction does not promise.
