# Portable payer session — one script, local or hosted

`demo/portable-session.sh` runs one complete payer session against any running
Thunderpolt stack and proves the core economics on the way through:

1. **Open a channel (onchain).** Approve + deposit escrow into the ChannelHub,
   binding a hot session key. The payer's only setup transactions.
2. **Run an application session (offchain).** N cumulative EIP-712 vouchers
   paid into the reference site's cheapest route via real `X-PAYMENT` headers.
   Zero onchain transactions per payment.
3. **Settle (onchain, automatic).** The operator's epoch cycle posts a Merkle
   root and the merchant's claim lands. The script verifies settlement by the
   hub's onchain `epochClaimed(merchant)` cursor advancing by at least this
   session's total — a per-merchant check that cannot false-pass on a stack
   other payers are also using.

The invariant it prints at the end: **zero onchain transactions per payment**.
A run's chain footprint is constant — two payer setup transactions plus the
operator's epoch root and one merchant claim — whether 10 payments streamed or
10,000.

Unlike `just demo` (which builds and orchestrates everything itself), this
script is a **client**. It needs only `bash`, `curl`, `jq`, `openssl`, and
Foundry's `cast` — no repo build — and touches only public surfaces: the
site's `/meta`, the dispenser, the facilitator, the explorer, and the chain
RPC. That is what makes it portable: point it at a different stack and nothing
else changes.

## Quickstart (local)

Terminal 1 — start the full local stack (stays up until Ctrl-C):

```sh
just sp7-stack
```

Terminal 2 — run the session:

```sh
just demo-session
# or directly:
./demo/portable-session.sh
```

Expect ~30 seconds of setup and payments, then a 2–3 minute wait while the
operator's epoch cycle settles (epoch close 60s + auto-claim 120s on the
sp7-stack defaults).

## Against a hosted stack

Only the two URLs change; everything else self-discovers from `$SITE_URL/meta`:

```sh
SITE_URL=http://<host>:8594 RPC_URL=http://<host>:8590 ./demo/portable-session.sh
```

## Options

| Env var | Default | What it does |
| --- | --- | --- |
| `SITE_URL` | `http://127.0.0.1:8594` | The reference merchant site; `/meta` here is the discovery root |
| `RPC_URL` | `http://127.0.0.1:8590` | Chain RPC, used for the deposit and the cursor checks |
| `N_PAYMENTS` | `10` | How many offchain payments to stream |
| `HUB_INDEX` | `0` | Which hub from `/meta`'s `hubs[]` to open the channel on |
| `SETTLE_DEADLINE_SECS` | `420` | How long step 9 waits for the automatic settlement |
| `REUSE_DIR` | unset | Persist the wallet here; a second run **skips funding and the deposit entirely** and resumes the cumulative where it left off — the fund-once, pay-many demonstration |

Channel reuse, the two-command version:

```sh
REUSE_DIR=~/.tp-demo-wallet ./demo/portable-session.sh   # opens the channel
REUSE_DIR=~/.tp-demo-wallet ./demo/portable-session.sh   # same channel, no new chain setup
```

## Requirements and known behavior

- **The stack must run automatic epoch + claim timers** (the `sp7-stack`
  defaults: `EPOCH_INTERVAL=60`, `CLAIM_INTERVAL=120`). On a stack started
  with those set to `0`, step 9's settlement wait times out by design — that
  stack flavor expects a manual `/epoch/close`, which this client deliberately
  does not call (it is an operator surface).
- **First-payment retry.** The hub's deposit watcher can lag the explorer's
  confirmation by a few seconds, and a payment landing in that gap declines
  as `invalid-signature` (the hub has no session-key binding for the payer
  yet). The script retries the first payment for up to 45 seconds; see
  `docs/agents/PAYING.md` §5's readiness guidance.
- **Discovery goes through the SITE's `/meta`**, not the facilitator's — the
  facilitator's `/meta` intentionally omits the dispenser/explorer URLs, and
  engine URLs may be private.
- Keys are throwaway devnet keys, generated per run into a `mktemp` directory
  (or `REUSE_DIR`), never printed, and never written into the repository.
- For the full from-zero recipe this script condenses — including error
  etiquette, resync rules, and the concurrent-payment pattern — read
  `docs/agents/PAYING.md`.
